Move OBB file reading to DefaultContainerService

The system_server shouldn't touch files on the SD card. This change
moves the things that touch the SD card out to the
DefaultContainerService so that it will get killed if the SD card goes
away instead of the system_server.

Change-Id: I0aefa085be4b194768527195532ee6dddc801cfc
diff --git a/Android.mk b/Android.mk
index b6f25047..b1f43f8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -121,6 +121,7 @@
 	core/java/android/os/storage/IMountService.aidl \
 	core/java/android/os/storage/IMountServiceListener.aidl \
 	core/java/android/os/storage/IMountShutdownObserver.aidl \
+	core/java/android/os/storage/IObbActionListener.aidl \
 	core/java/android/os/INetworkManagementService.aidl \
 	core/java/android/os/INetStatService.aidl \
 	core/java/android/os/IPermissionController.aidl \
diff --git a/api/current.xml b/api/current.xml
index 3d35388..65a8a8e 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -129531,6 +129531,8 @@
 >
 <parameter name="filename" type="java.lang.String">
 </parameter>
+<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
+</exception>
 </method>
 <method name="mountObb"
  return="boolean"
@@ -129561,6 +129563,8 @@
 </parameter>
 <parameter name="force" type="boolean">
 </parameter>
+<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
+</exception>
 </method>
 </class>
 </package>
diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
index ca7efe7..5c69214 100644
--- a/core/java/android/os/storage/IMountService.aidl
+++ b/core/java/android/os/storage/IMountService.aidl
@@ -19,6 +19,7 @@
 
 import android.os.storage.IMountServiceListener;
 import android.os.storage.IMountShutdownObserver;
+import android.os.storage.IObbActionListener;
 
 /** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
  * In particular, the ordering of the methods below must match the 
@@ -156,14 +157,20 @@
     /**
      * Mounts an Opaque Binary Blob (OBB) with the specified decryption key and only
      * allows the calling process's UID access to the contents.
+     *
+     * MountService will call back to the supplied IObbActionListener to inform
+     * it of the terminal state of the call.
      */
-    int mountObb(String filename, String key);
+    void mountObb(String filename, String key, IObbActionListener token);
 
     /**
      * Unmounts an Opaque Binary Blob (OBB). When the force flag is specified, any
      * program using it will be forcibly killed to unmount the image.
+     *
+     * MountService will call back to the supplied IObbActionListener to inform
+     * it of the terminal state of the call.
      */
-    int unmountObb(String filename, boolean force);
+    void unmountObb(String filename, boolean force, IObbActionListener token);
 
     /**
      * Checks whether the specified Opaque Binary Blob (OBB) is mounted somewhere.
diff --git a/core/java/android/os/storage/IObbActionListener.aidl b/core/java/android/os/storage/IObbActionListener.aidl
new file mode 100644
index 0000000..78d7a9e
--- /dev/null
+++ b/core/java/android/os/storage/IObbActionListener.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+/**
+ * Callback class for receiving events from MountService about
+ * Opaque Binary Blobs (OBBs).
+ *
+ * @hide - Applications should use android.os.storage.StorageManager
+ * to interact with OBBs.
+ */
+interface IObbActionListener {
+    /**
+     * Return from an OBB action result.
+     *
+     * @param filename the path to the OBB the operation was performed on
+     * @param returnCode status of the operation
+     */
+    void onObbResult(String filename, String status);
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 96bf2d5..cb1794f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,28 +16,14 @@
 
 package android.os.storage;
 
-import android.content.Context;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.Parcelable;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.storage.IMountService;
-import android.os.storage.IMountServiceListener;
 import android.util.Log;
-import android.util.SparseArray;
 
-import java.io.FileDescriptor;
-import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
 
 /**
  * StorageManager is the interface to the systems storage service.
@@ -87,6 +73,17 @@
     }
 
     /**
+     * Binder listener for OBB action results.
+     */
+    private final ObbActionBinderListener mObbActionListener = new ObbActionBinderListener();
+    private class ObbActionBinderListener extends IObbActionListener.Stub {
+        @Override
+        public void onObbResult(String filename, String status) throws RemoteException {
+            Log.i(TAG, "filename = " + filename + ", result = " + status);
+        }
+    }
+
+    /**
      * Private base class for messages sent between the callback thread
      * and the target looper handler.
      */
@@ -299,12 +296,23 @@
     }
 
     /**
-     * Mount an OBB file.
+     * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
+     * specified, it is supplied to the mounting process to be used in any
+     * encryption used in the OBB.
+     * <p>
+     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
+     * file matches a package ID that is owned by the calling program's UID.
+     * That is, shared UID applications can obtain access to any other
+     * application's OBB that shares its UID.
+     * 
+     * @param filename the path to the OBB file
+     * @param key decryption key
+     * @return whether the mount call was successfully queued or not
      */
     public boolean mountObb(String filename, String key) {
         try {
-            return mMountService.mountObb(filename, key)
-                    == StorageResultCode.OperationSucceeded;
+            mMountService.mountObb(filename, key, mObbActionListener);
+            return true;
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to mount OBB", e);
         }
@@ -313,12 +321,24 @@
     }
 
     /**
-     * Mount an OBB file.
+     * Unmount an Opaque Binary Blob (OBB) file. If the <code>force</code> flag
+     * is true, it will kill any application needed to unmount the given OBB.
+     * <p>
+     * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
+     * file matches a package ID that is owned by the calling program's UID.
+     * That is, shared UID applications can obtain access to any other
+     * application's OBB that shares its UID.
+     * 
+     * @param filename path to the OBB file
+     * @param force whether to kill any programs using this in order to unmount
+     *            it
+     * @return whether the unmount call was successfully queued or not
+     * @throws IllegalArgumentException when OBB is not already mounted
      */
-    public boolean unmountObb(String filename, boolean force) {
+    public boolean unmountObb(String filename, boolean force) throws IllegalArgumentException {
         try {
-            return mMountService.unmountObb(filename, force)
-                    == StorageResultCode.OperationSucceeded;
+            mMountService.unmountObb(filename, force, mObbActionListener);
+            return true;
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to mount OBB", e);
         }
@@ -326,7 +346,13 @@
         return false;
     }
 
-    public boolean isObbMounted(String filename) {
+    /**
+     * Check whether an Opaque Binary Blob (OBB) is mounted or not.
+     * 
+     * @param filename path to OBB image
+     * @return true if OBB is mounted; false if not mounted or on error
+     */
+    public boolean isObbMounted(String filename) throws IllegalArgumentException {
         try {
             return mMountService.isObbMounted(filename);
         } catch (RemoteException e) {
@@ -337,13 +363,21 @@
     }
 
     /**
-     * Check the mounted path of an OBB file.
+     * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
+     * give you the path to where you can obtain access to the internals of the
+     * OBB.
+     * 
+     * @param filename path to OBB image
+     * @return absolute path to mounted OBB image data or <code>null</code> if
+     *         not mounted or exception encountered trying to read status
      */
     public String getMountedObbPath(String filename) {
         try {
             return mMountService.getMountedObbPath(filename);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to find mounted path for OBB", e);
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Couldn't read OBB file", e);
         }
 
         return null;
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 89649a9..5d1f632 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -19,6 +19,7 @@
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.content.pm.PackageInfoLite;
+import android.content.res.ObbInfo;
 
 interface IMediaContainerService {
     String copyResourceToContainer(in Uri packageURI,
@@ -28,4 +29,5 @@
                 in ParcelFileDescriptor outStream);
     PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags);
     boolean checkFreeStorage(boolean external, in Uri fileUri);
+    ObbInfo getObbInfo(String filename);
 }
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index f1c6532..c6e0a24 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -24,6 +24,8 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.res.ObbInfo;
+import android.content.res.ObbScanner;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.IBinder;
@@ -142,6 +144,10 @@
         public boolean checkFreeStorage(boolean external, Uri fileUri) {
             return checkFreeStorageInner(external, fileUri);
         }
+
+        public ObbInfo getObbInfo(String filename) {
+            return ObbScanner.getObbInfo(filename);
+        }
     };
 
     public DefaultContainerService() {
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index d7b92ec..344bfc1 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -16,34 +16,42 @@
 
 package com.android.server;
 
+import com.android.internal.app.IMediaContainerService;
 import com.android.server.am.ActivityManagerService;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.res.ObbInfo;
-import android.content.res.ObbScanner;
 import android.net.Uri;
-import android.os.storage.IMountService;
-import android.os.storage.IMountServiceListener;
-import android.os.storage.IMountShutdownObserver;
-import android.os.storage.StorageResultCode;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.Environment;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.storage.IMountService;
+import android.os.storage.IMountServiceListener;
+import android.os.storage.IMountShutdownObserver;
+import android.os.storage.IObbActionListener;
+import android.os.storage.StorageResultCode;
 import android.util.Slog;
+
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 
 /**
  * MountService implements back-end services for platform storage
@@ -135,9 +143,77 @@
     final private HashSet<String> mAsecMountSet = new HashSet<String>();
 
     /**
-     * Private hash of currently mounted filesystem images.
+     * Mounted OBB tracking information. Used to track the current state of all
+     * OBBs.
      */
-    final private HashSet<String> mObbMountSet = new HashSet<String>();
+    final private Map<IObbActionListener, ObbState> mObbMounts = new HashMap<IObbActionListener, ObbState>();
+    final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
+
+    class ObbState implements IBinder.DeathRecipient {
+        public ObbState(String filename, IObbActionListener token, int callerUid) {
+            this.filename = filename;
+            this.token = token;
+            this.callerUid = callerUid;
+            mounted = false;
+        }
+
+        // OBB source filename
+        String filename;
+
+        // Token of remote Binder caller
+        IObbActionListener token;
+
+        // Binder.callingUid()
+        public int callerUid;
+
+        // Whether this is mounted currently.
+        boolean mounted;
+
+        @Override
+        public void binderDied() {
+            ObbAction action = new UnmountObbAction(this, true);
+            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+
+            removeObbState(this);
+
+            token.asBinder().unlinkToDeath(this, 0);
+        }
+    }
+
+    // OBB Action Handler
+    final private ObbActionHandler mObbActionHandler;
+
+    // OBB action handler messages
+    private static final int OBB_RUN_ACTION = 1;
+    private static final int OBB_MCS_BOUND = 2;
+    private static final int OBB_MCS_UNBIND = 3;
+    private static final int OBB_MCS_RECONNECT = 4;
+    private static final int OBB_MCS_GIVE_UP = 5;
+
+    /*
+     * Default Container Service information
+     */
+    static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
+            "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService");
+
+    final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
+
+    class DefaultContainerConnection implements ServiceConnection {
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG_OBB)
+                Slog.i(TAG, "onServiceConnected");
+            IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
+            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG_OBB)
+                Slog.i(TAG, "onServiceDisconnected");
+        }
+    };
+
+    // Used in the ObbActionHandler
+    private IMediaContainerService mContainerService = null;
 
     // Handler messages
     private static final int H_UNMOUNT_PM_UPDATE = 1;
@@ -359,7 +435,7 @@
 
         public void binderDied() {
             if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
-            synchronized(mListeners) {
+            synchronized (mListeners) {
                 mListeners.remove(this);
                 mListener.asBinder().unlinkToDeath(this, 0);
             }
@@ -904,6 +980,9 @@
         mHandlerThread.start();
         mHandler = new MountServiceHandler(mHandlerThread.getLooper());
 
+        // Add OBB Action Handler to MountService thread.
+        mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
+
         /*
          * Vold does not run in the simulator, so pretend the connector thread
          * ran and did its thing.
@@ -1338,12 +1417,16 @@
         mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
     }
 
-    private boolean isCallerOwnerOfPackage(String packageName) {
+    private boolean isCallerOwnerOfPackageOrSystem(String packageName) {
         final int callerUid = Binder.getCallingUid();
-        return isUidOwnerOfPackage(packageName, callerUid);
+        return isUidOwnerOfPackageOrSystem(packageName, callerUid);
     }
 
-    private boolean isUidOwnerOfPackage(String packageName, int callerUid) {
+    private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
+        if (callerUid == android.os.Process.SYSTEM_UID) {
+            return true;
+        }
+
         if (packageName == null) {
             return false;
         }
@@ -1362,12 +1445,6 @@
         waitForReady();
         warnOnNotMounted();
 
-        // XXX replace with call to IMediaContainerService
-        ObbInfo obbInfo = ObbScanner.getObbInfo(filename);
-        if (!isCallerOwnerOfPackage(obbInfo.packageName)) {
-            throw new IllegalArgumentException("Caller package does not match OBB file");
-        }
-
         try {
             ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename));
             String []tok = rsp.get(0).split(" ");
@@ -1379,7 +1456,7 @@
         } catch (NativeDaemonConnectorException e) {
             int code = e.getCode();
             if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                throw new IllegalArgumentException(String.format("OBB '%s' not found", filename));
+                return null;
             } else {
                 throw new IllegalStateException(String.format("Unexpected response code %d", code));
             }
@@ -1387,95 +1464,390 @@
     }
 
     public boolean isObbMounted(String filename) {
-        waitForReady();
-        warnOnNotMounted();
-
-        // XXX replace with call to IMediaContainerService
-        ObbInfo obbInfo = ObbScanner.getObbInfo(filename);
-        if (!isCallerOwnerOfPackage(obbInfo.packageName)) {
-            throw new IllegalArgumentException("Caller package does not match OBB file");
-        }
-
-        synchronized (mObbMountSet) {
-            return mObbMountSet.contains(filename);
+        synchronized (mObbMounts) {
+            return mObbPathToStateMap.containsKey(filename);
         }
     }
 
-    public int mountObb(String filename, String key) {
+    public void mountObb(String filename, String key, IObbActionListener token) {
         waitForReady();
         warnOnNotMounted();
 
-        synchronized (mObbMountSet) {
-            if (mObbMountSet.contains(filename)) {
-                return StorageResultCode.OperationFailedStorageMounted;
+        final ObbState obbState;
+
+        synchronized (mObbMounts) {
+            if (isObbMounted(filename)) {
+                throw new IllegalArgumentException("OBB file is already mounted");
             }
+
+            if (mObbMounts.containsKey(token)) {
+                throw new IllegalArgumentException("You may only have one OBB mounted at a time");
+            }
+
+            final int callerUid = Binder.getCallingUid();
+            obbState = new ObbState(filename, token, callerUid);
+            addObbState(obbState);
         }
 
-        final int callerUid = Binder.getCallingUid();
-
-        // XXX replace with call to IMediaContainerService
-        ObbInfo obbInfo = ObbScanner.getObbInfo(filename);
-        if (!isUidOwnerOfPackage(obbInfo.packageName, callerUid)) {
-            throw new IllegalArgumentException("Caller package does not match OBB file");
-        }
-
-        if (key == null) {
-            key = "none";
-        }
-
-        int rc = StorageResultCode.OperationSucceeded;
-        String cmd = String.format("obb mount %s %s %d", filename, key, callerUid);
         try {
-            mConnector.doCommand(cmd);
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code != VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
+            token.asBinder().linkToDeath(obbState, 0);
+        } catch (RemoteException rex) {
+            Slog.e(TAG, "Failed to link to listener death");
         }
 
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mObbMountSet) {
-                mObbMountSet.add(filename);
-            }
-        }
-        return rc;
+        MountObbAction action = new MountObbAction(obbState, key);
+        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+
+        if (DEBUG_OBB)
+            Slog.i(TAG, "Send to OBB handler: " + action.toString());
     }
 
-    public int unmountObb(String filename, boolean force) {
-        waitForReady();
-        warnOnNotMounted();
+    public void unmountObb(String filename, boolean force, IObbActionListener token) {
+        final ObbState obbState;
 
-        ObbInfo obbInfo = ObbScanner.getObbInfo(filename);
-        if (!isCallerOwnerOfPackage(obbInfo.packageName)) {
-            throw new IllegalArgumentException("Caller package does not match OBB file");
+        synchronized (mObbMounts) {
+            if (!isObbMounted(filename)) {
+                throw new IllegalArgumentException("OBB is not mounted");
+            }
+            obbState = mObbPathToStateMap.get(filename);
         }
 
-        synchronized (mObbMountSet) {
-            if (!mObbMountSet.contains(filename)) {
-                return StorageResultCode.OperationFailedStorageNotMounted;
-            }
-         }
+        UnmountObbAction action = new UnmountObbAction(obbState, force);
+        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
 
-        int rc = StorageResultCode.OperationSucceeded;
-        String cmd = String.format("obb unmount %s%s", filename, (force ? " force" : ""));
-        try {
-            mConnector.doCommand(cmd);
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedStorageBusy;
+        if (DEBUG_OBB)
+            Slog.i(TAG, "Send to OBB handler: " + action.toString());
+    }
+
+    private void addObbState(ObbState obbState) {
+        synchronized (mObbMounts) {
+            mObbMounts.put(obbState.token, obbState);
+            mObbPathToStateMap.put(obbState.filename, obbState);
+        }
+    }
+
+    private void removeObbState(ObbState obbState) {
+        synchronized (mObbMounts) {
+            mObbMounts.remove(obbState.token);
+            mObbPathToStateMap.remove(obbState.filename);
+        }
+    }
+
+    private class ObbActionHandler extends Handler {
+        private boolean mBound = false;
+        private List<ObbAction> mActions = new LinkedList<ObbAction>();
+
+        ObbActionHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case OBB_RUN_ACTION: {
+                    ObbAction action = (ObbAction) msg.obj;
+
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
+
+                    // If a bind was already initiated we don't really
+                    // need to do anything. The pending install
+                    // will be processed later on.
+                    if (!mBound) {
+                        // If this is the only one pending we might
+                        // have to bind to the service again.
+                        if (!connectToService()) {
+                            Slog.e(TAG, "Failed to bind to media container service");
+                            action.handleError();
+                            return;
+                        } else {
+                            // Once we bind to the service, the first
+                            // pending request will be processed.
+                            mActions.add(action);
+                        }
+                    } else {
+                        // Already bound to the service. Just make
+                        // sure we trigger off processing the first request.
+                        if (mActions.size() == 0) {
+                            mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
+                        }
+
+                        mActions.add(action);
+                    }
+                    break;
+                }
+                case OBB_MCS_BOUND: {
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_MCS_BOUND");
+                    if (msg.obj != null) {
+                        mContainerService = (IMediaContainerService) msg.obj;
+                    }
+                    if (mContainerService == null) {
+                        // Something seriously wrong. Bail out
+                        Slog.e(TAG, "Cannot bind to media container service");
+                        for (ObbAction action : mActions) {
+                            // Indicate service bind error
+                            action.handleError();
+                        }
+                        mActions.clear();
+                    } else if (mActions.size() > 0) {
+                        ObbAction action = mActions.get(0);
+                        if (action != null) {
+                            action.execute(this);
+                        }
+                    } else {
+                        // Should never happen ideally.
+                        Slog.w(TAG, "Empty queue");
+                    }
+                    break;
+                }
+                case OBB_MCS_RECONNECT: {
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_MCS_RECONNECT");
+                    if (mActions.size() > 0) {
+                        if (mBound) {
+                            disconnectService();
+                        }
+                        if (!connectToService()) {
+                            Slog.e(TAG, "Failed to bind to media container service");
+                            for (ObbAction action : mActions) {
+                                // Indicate service bind error
+                                action.handleError();
+                            }
+                            mActions.clear();
+                        }
+                    }
+                    break;
+                }
+                case OBB_MCS_UNBIND: {
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_MCS_UNBIND");
+
+                    // Delete pending install
+                    if (mActions.size() > 0) {
+                        mActions.remove(0);
+                    }
+                    if (mActions.size() == 0) {
+                        if (mBound) {
+                            disconnectService();
+                        }
+                    } else {
+                        // There are more pending requests in queue.
+                        // Just post MCS_BOUND message to trigger processing
+                        // of next pending install.
+                        mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
+                    }
+                    break;
+                }
+                case OBB_MCS_GIVE_UP: {
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_MCS_GIVE_UP");
+                    mActions.remove(0);
+                    break;
+                }
+            }
+        }
+
+        private boolean connectToService() {
+            if (DEBUG_OBB)
+                Slog.i(TAG, "Trying to bind to DefaultContainerService");
+
+            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+            if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
+                mBound = true;
+                return true;
+            }
+            return false;
+        }
+
+        private void disconnectService() {
+            mContainerService = null;
+            mBound = false;
+            mContext.unbindService(mDefContainerConn);
+        }
+    }
+
+    abstract class ObbAction {
+        private static final int MAX_RETRIES = 3;
+        private int mRetries;
+
+        ObbState mObbState;
+
+        ObbAction(ObbState obbState) {
+            mObbState = obbState;
+        }
+
+        public void execute(ObbActionHandler handler) {
+            try {
+                if (DEBUG_OBB)
+                    Slog.i(TAG, "Starting to execute action: " + this.toString());
+                mRetries++;
+                if (mRetries > MAX_RETRIES) {
+                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_GIVE_UP);
+                    handleError();
+                    return;
+                } else {
+                    handleExecute();
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "Posting install MCS_UNBIND");
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
+                }
+            } catch (RemoteException e) {
+                if (DEBUG_OBB)
+                    Slog.i(TAG, "Posting install MCS_RECONNECT");
+                mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
+            } catch (Exception e) {
+                if (DEBUG_OBB)
+                    Slog.d(TAG, "Error handling OBB action", e);
+                handleError();
+            }
+        }
+
+        abstract void handleExecute() throws RemoteException;
+        abstract void handleError();
+    }
+
+    class MountObbAction extends ObbAction {
+        private String mKey;
+
+        MountObbAction(ObbState obbState, String key) {
+            super(obbState);
+            mKey = key;
+        }
+
+        public void handleExecute() throws RemoteException {
+            ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
+            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
+                throw new IllegalArgumentException("Caller package does not match OBB file");
+            }
+
+            if (mKey == null) {
+                mKey = "none";
+            }
+
+            int rc = StorageResultCode.OperationSucceeded;
+            String cmd = String.format("obb mount %s %s %d", mObbState.filename, mKey,
+                    mObbState.callerUid);
+            try {
+                mConnector.doCommand(cmd);
+            } catch (NativeDaemonConnectorException e) {
+                int code = e.getCode();
+                if (code != VoldResponseCode.OpFailedStorageBusy) {
+                    rc = StorageResultCode.OperationFailedInternalError;
+                }
+            }
+
+            if (rc == StorageResultCode.OperationSucceeded) {
+                try {
+                    mObbState.token.onObbResult(mObbState.filename, "mounted");
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+                }
             } else {
-                rc = StorageResultCode.OperationFailedInternalError;
+                Slog.e(TAG, "Couldn't mount OBB file");
+
+                // We didn't succeed, so remove this from the mount-set.
+                removeObbState(mObbState);
             }
         }
 
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mObbMountSet) {
-                mObbMountSet.remove(filename);
+        public void handleError() {
+            removeObbState(mObbState);
+
+            try {
+                mObbState.token.onObbResult(mObbState.filename, "error");
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename);
             }
         }
-        return rc;
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("MountObbAction{");
+            sb.append("filename=");
+            sb.append(mObbState.filename);
+            sb.append(",callerUid=");
+            sb.append(mObbState.callerUid);
+            sb.append(",token=");
+            sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL");
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    class UnmountObbAction extends ObbAction {
+        private boolean mForceUnmount;
+
+        UnmountObbAction(ObbState obbState, boolean force) {
+            super(obbState);
+            mForceUnmount = force;
+        }
+
+        public void handleExecute() throws RemoteException {
+            ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
+
+            if (!isCallerOwnerOfPackageOrSystem(obbInfo.packageName)) {
+                throw new IllegalArgumentException("Caller package does not match OBB file");
+            }
+
+            int rc = StorageResultCode.OperationSucceeded;
+            String cmd = String.format("obb unmount %s%s", mObbState.filename,
+                    (mForceUnmount ? " force" : ""));
+            try {
+                mConnector.doCommand(cmd);
+            } catch (NativeDaemonConnectorException e) {
+                int code = e.getCode();
+                if (code == VoldResponseCode.OpFailedStorageBusy) {
+                    rc = StorageResultCode.OperationFailedStorageBusy;
+                } else {
+                    rc = StorageResultCode.OperationFailedInternalError;
+                }
+            }
+
+            if (rc == StorageResultCode.OperationSucceeded) {
+                removeObbState(mObbState);
+
+                try {
+                    mObbState.token.onObbResult(mObbState.filename, "unmounted");
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+                }
+            } else {
+                try {
+                    mObbState.token.onObbResult(mObbState.filename, "error");
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+                }
+            }
+        }
+
+        public void handleError() {
+            removeObbState(mObbState);
+
+            try {
+                mObbState.token.onObbResult(mObbState.filename, "error");
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("UnmountObbAction{");
+            sb.append("filename=");
+            sb.append(mObbState.filename != null ? mObbState.filename : "null");
+            sb.append(",force=");
+            sb.append(mForceUnmount);
+            sb.append(",callerUid=");
+            sb.append(mObbState.callerUid);
+            sb.append(",token=");
+            sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
+            sb.append('}');
+            return sb.toString();
+        }
     }
 }