Merge "New system property "sys.boot_completed" set to 1 when boot completed." into gingerbread
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/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index b18419d..7acd9ba 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -727,6 +727,9 @@
     }
 
     long getCacheTotalSize() {
+        if (mCacheDatabase == null) {
+            return 0;
+        }
         long size = 0;
         Cursor cursor = null;
         final String query = "SELECT SUM(contentlength) as sum FROM cache";
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/include/utils/ObbFile.h b/include/utils/ObbFile.h
index 075927c..d2ca82e 100644
--- a/include/utils/ObbFile.h
+++ b/include/utils/ObbFile.h
@@ -35,6 +35,8 @@
     bool readFrom(int fd);
     bool writeTo(const char* filename);
     bool writeTo(int fd);
+    bool removeFrom(const char* filename);
+    bool removeFrom(int fd);
 
     const char* getFileName() const {
         return mFileName;
@@ -78,6 +80,8 @@
 
     size_t mFileSize;
 
+    size_t mFooterStart;
+
     unsigned char* mReadBuf;
 
     bool parseObbFile(int fd);
diff --git a/libs/utils/ObbFile.cpp b/libs/utils/ObbFile.cpp
index fe49300..adedf0c 100644
--- a/libs/utils/ObbFile.cpp
+++ b/libs/utils/ObbFile.cpp
@@ -156,9 +156,9 @@
             return false;
         }
 
-        if (footerSize < kFooterMinSize) {
-            LOGW("claimed footer size is too small (%08zx; minimum size is 0x%x)\n",
-                    footerSize, kFooterMinSize);
+        if (footerSize < (kFooterMinSize - kFooterTagSize)) {
+            LOGW("claimed footer size is too small (0x%zx; minimum size is 0x%x)\n",
+                    footerSize, kFooterMinSize - kFooterTagSize);
             return false;
         }
     }
@@ -169,6 +169,8 @@
         return false;
     }
 
+    mFooterStart = fileOffset;
+
     char* scanBuf = (char*)malloc(footerSize);
     if (scanBuf == NULL) {
         LOGW("couldn't allocate scanBuf: %s\n", strerror(errno));
@@ -293,4 +295,38 @@
     return true;
 }
 
+bool ObbFile::removeFrom(const char* filename)
+{
+    int fd;
+    bool success = false;
+
+    fd = ::open(filename, O_RDWR);
+    if (fd < 0) {
+        goto out;
+    }
+    success = removeFrom(fd);
+    close(fd);
+
+out:
+    if (!success) {
+        LOGW("failed to remove signature from %s: %s\n", filename, strerror(errno));
+    }
+    return success;
+}
+
+bool ObbFile::removeFrom(int fd)
+{
+    if (fd < 0) {
+        return false;
+    }
+
+    if (!readFrom(fd)) {
+        return false;
+    }
+
+    ftruncate(fd, mFooterStart);
+
+    return true;
+}
+
 }
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 9c48daf..3e31d61 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -244,6 +244,7 @@
 
 void CameraSource::signalBufferReturned(MediaBuffer *buffer) {
     LOGV("signalBufferReturned: %p", buffer->data());
+    Mutex::Autolock autoLock(mLock);
     for (List<sp<IMemory> >::iterator it = mFramesBeingEncoded.begin();
          it != mFramesBeingEncoded.end(); ++it) {
         if ((*it)->pointer() ==  buffer->data()) {
@@ -312,6 +313,7 @@
                 (*buffer)->setObserver(this);
                 (*buffer)->add_ref();
                 (*buffer)->meta_data()->setInt64(kKeyTime, frameTime);
+
                 return OK;
             }
         }
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();
+        }
     }
 }
 
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
index a1bdd2f..611e3ea 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
@@ -42,7 +42,7 @@
             SipProfile profile = new SipProfile.Builder(sipUri).build();
             return new SipPhone(context, phoneNotifier, profile);
         } catch (ParseException e) {
-            Log.w("SipPhoneProxy", "setPhone", e);
+            Log.w("SipPhoneFactory", "makePhone", e);
             return null;
         }
     }
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java
deleted file mode 100644
index 7cc1a9b..0000000
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * 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 com.android.internal.telephony.sip;
-
-import com.android.internal.telephony.*;
-import com.android.internal.telephony.gsm.NetworkInfo;
-import com.android.internal.telephony.test.SimulatedRadioControl;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.telephony.CellLocation;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Temporary. Will be removed after integrating with CallManager.
- * (TODO)
- * @hide
- */
-public class SipPhoneProxy implements Phone {
-    private static final String LOG_TAG = "PHONE";
-
-    private static SipPhoneProxy sPhoneProxy = new SipPhoneProxy();
-
-    public static SipPhoneProxy getInstance() {
-        return sPhoneProxy;
-    }
-
-    private SipPhone mActivePhone;
-    private CallProxy mRingingCall = new CallProxy();
-    private CallProxy mForegroundCall = new CallProxy();
-    private CallProxy mBackgroundCall = new CallProxy();
-
-    private SipPhoneProxy() {
-    }
-
-    public void onNewCall(Object call) {
-        if (mActivePhone.canTake(call)) {
-            Log.v("SipPhoneProxy", "onNewCall(): call taken: " + call);
-        } else {
-            Log.v("SipPhoneProxy", "onNewCall(): call dropped: " + call);
-        }
-    }
-
-    public synchronized void setPhone(SipPhone phone) {
-        if (phone == null) return;
-        if (mActivePhone != null) phone.migrateFrom(mActivePhone);
-        mActivePhone = phone;
-        mForegroundCall.setTarget(phone.getForegroundCall());
-        mBackgroundCall.setTarget(phone.getBackgroundCall());
-        mRingingCall.setTarget(phone.getRingingCall());
-    }
-
-    public synchronized Call getForegroundCall() {
-        return mForegroundCall;
-    }
-
-    public synchronized Call getBackgroundCall() {
-        return mBackgroundCall;
-    }
-
-    public synchronized Call getRingingCall() {
-        return mRingingCall;
-    }
-
-
-    public ServiceState getServiceState() {
-        return mActivePhone.getServiceState();
-    }
-
-    public CellLocation getCellLocation() {
-        return mActivePhone.getCellLocation();
-    }
-
-    public DataState getDataConnectionState() {
-        return mActivePhone.getDataConnectionState();
-    }
-
-    public DataActivityState getDataActivityState() {
-        return mActivePhone.getDataActivityState();
-    }
-
-    public Context getContext() {
-        return mActivePhone.getContext();
-    }
-
-    public void disableDnsCheck(boolean b) {
-        mActivePhone.disableDnsCheck(b);
-    }
-
-    public boolean isDnsCheckDisabled() {
-        return mActivePhone.isDnsCheckDisabled();
-    }
-
-    public State getState() {
-        return mActivePhone.getState();
-    }
-
-    public String getPhoneName() {
-        return mActivePhone.getPhoneName();
-    }
-
-    public int getPhoneType() {
-        return mActivePhone.getPhoneType();
-    }
-
-    public String[] getActiveApnTypes() {
-        return mActivePhone.getActiveApnTypes();
-    }
-
-    public String getActiveApn() {
-        return mActivePhone.getActiveApn();
-    }
-
-    public SignalStrength getSignalStrength() {
-        return mActivePhone.getSignalStrength();
-    }
-
-    public void registerForUnknownConnection(Handler h, int what, Object obj) {
-        mActivePhone.registerForUnknownConnection(h, what, obj);
-    }
-
-    public void unregisterForUnknownConnection(Handler h) {
-        mActivePhone.unregisterForUnknownConnection(h);
-    }
-
-    public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) {
-        mActivePhone.registerForPreciseCallStateChanged(h, what, obj);
-    }
-
-    public void unregisterForPreciseCallStateChanged(Handler h) {
-        mActivePhone.unregisterForPreciseCallStateChanged(h);
-    }
-
-    public void registerForNewRingingConnection(Handler h, int what, Object obj) {
-        mActivePhone.registerForNewRingingConnection(h, what, obj);
-    }
-
-    public void unregisterForNewRingingConnection(Handler h) {
-        mActivePhone.unregisterForNewRingingConnection(h);
-    }
-
-    public void registerForIncomingRing(Handler h, int what, Object obj) {
-        mActivePhone.registerForIncomingRing(h, what, obj);
-    }
-
-    public void unregisterForIncomingRing(Handler h) {
-        mActivePhone.unregisterForIncomingRing(h);
-    }
-
-    public void registerForDisconnect(Handler h, int what, Object obj) {
-        mActivePhone.registerForDisconnect(h, what, obj);
-    }
-
-    public void unregisterForDisconnect(Handler h) {
-        mActivePhone.unregisterForDisconnect(h);
-    }
-
-    public void registerForMmiInitiate(Handler h, int what, Object obj) {
-        mActivePhone.registerForMmiInitiate(h, what, obj);
-    }
-
-    public void unregisterForMmiInitiate(Handler h) {
-        mActivePhone.unregisterForMmiInitiate(h);
-    }
-
-    public void registerForMmiComplete(Handler h, int what, Object obj) {
-        mActivePhone.registerForMmiComplete(h, what, obj);
-    }
-
-    public void unregisterForMmiComplete(Handler h) {
-        mActivePhone.unregisterForMmiComplete(h);
-    }
-
-    public List<? extends MmiCode> getPendingMmiCodes() {
-        return mActivePhone.getPendingMmiCodes();
-    }
-
-    public void sendUssdResponse(String ussdMessge) {
-        mActivePhone.sendUssdResponse(ussdMessge);
-    }
-
-    public void registerForServiceStateChanged(Handler h, int what, Object obj) {
-        mActivePhone.registerForServiceStateChanged(h, what, obj);
-    }
-
-    public void unregisterForServiceStateChanged(Handler h) {
-        mActivePhone.unregisterForServiceStateChanged(h);
-    }
-
-    public void registerForSuppServiceNotification(Handler h, int what, Object obj) {
-        mActivePhone.registerForSuppServiceNotification(h, what, obj);
-    }
-
-    public void unregisterForSuppServiceNotification(Handler h) {
-        mActivePhone.unregisterForSuppServiceNotification(h);
-    }
-
-    public void registerForSuppServiceFailed(Handler h, int what, Object obj) {
-        mActivePhone.registerForSuppServiceFailed(h, what, obj);
-    }
-
-    public void unregisterForSuppServiceFailed(Handler h) {
-        mActivePhone.unregisterForSuppServiceFailed(h);
-    }
-
-    public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){
-        mActivePhone.registerForInCallVoicePrivacyOn(h,what,obj);
-    }
-
-    public void unregisterForInCallVoicePrivacyOn(Handler h){
-        mActivePhone.unregisterForInCallVoicePrivacyOn(h);
-    }
-
-    public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){
-        mActivePhone.registerForInCallVoicePrivacyOff(h,what,obj);
-    }
-
-    public void unregisterForInCallVoicePrivacyOff(Handler h){
-        mActivePhone.unregisterForInCallVoicePrivacyOff(h);
-    }
-
-    public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
-        mActivePhone.registerForCdmaOtaStatusChange(h,what,obj);
-    }
-
-    public void unregisterForCdmaOtaStatusChange(Handler h) {
-         mActivePhone.unregisterForCdmaOtaStatusChange(h);
-    }
-
-    public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
-        mActivePhone.registerForSubscriptionInfoReady(h, what, obj);
-    }
-
-    public void unregisterForSubscriptionInfoReady(Handler h) {
-        mActivePhone.unregisterForSubscriptionInfoReady(h);
-    }
-
-    public void registerForEcmTimerReset(Handler h, int what, Object obj) {
-        mActivePhone.registerForEcmTimerReset(h,what,obj);
-    }
-
-    public void unregisterForEcmTimerReset(Handler h) {
-        mActivePhone.unregisterForEcmTimerReset(h);
-    }
-
-    public void registerForRingbackTone(Handler h, int what, Object obj) {
-        mActivePhone.registerForRingbackTone(h,what,obj);
-    }
-
-    public void unregisterForRingbackTone(Handler h) {
-        mActivePhone.unregisterForRingbackTone(h);
-    }
-
-    public void registerForResendIncallMute(Handler h, int what, Object obj) {
-        mActivePhone.registerForResendIncallMute(h,what,obj);
-    }
-
-    public void unregisterForResendIncallMute(Handler h) {
-        mActivePhone.unregisterForResendIncallMute(h);
-    }
-
-    public boolean getIccRecordsLoaded() {
-        return mActivePhone.getIccRecordsLoaded();
-    }
-
-    public IccCard getIccCard() {
-        return mActivePhone.getIccCard();
-    }
-
-    public void acceptCall() throws CallStateException {
-        mActivePhone.acceptCall();
-    }
-
-    public void rejectCall() throws CallStateException {
-        mActivePhone.rejectCall();
-    }
-
-    public void switchHoldingAndActive() throws CallStateException {
-        mActivePhone.switchHoldingAndActive();
-    }
-
-    public boolean canConference() {
-        return mActivePhone.canConference();
-    }
-
-    public void conference() throws CallStateException {
-        mActivePhone.conference();
-    }
-
-    public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
-        mActivePhone.enableEnhancedVoicePrivacy(enable, onComplete);
-    }
-
-    public void getEnhancedVoicePrivacy(Message onComplete) {
-        mActivePhone.getEnhancedVoicePrivacy(onComplete);
-    }
-
-    public boolean canTransfer() {
-        return mActivePhone.canTransfer();
-    }
-
-    public void explicitCallTransfer() throws CallStateException {
-        mActivePhone.explicitCallTransfer();
-    }
-
-    public void clearDisconnected() {
-        mActivePhone.clearDisconnected();
-    }
-
-    public Connection dial(String dialString) throws CallStateException {
-        return mActivePhone.dial(dialString);
-    }
-
-    public Connection dial(String dialString, UUSInfo uusInfo) throws CallStateException {
-        return mActivePhone.dial(dialString);
-    }
-
-    public boolean handlePinMmi(String dialString) {
-        return mActivePhone.handlePinMmi(dialString);
-    }
-
-    public boolean handleInCallMmiCommands(String command) throws CallStateException {
-        return mActivePhone.handleInCallMmiCommands(command);
-    }
-
-    public void sendDtmf(char c) {
-        mActivePhone.sendDtmf(c);
-    }
-
-    public void startDtmf(char c) {
-        mActivePhone.startDtmf(c);
-    }
-
-    public void stopDtmf() {
-        mActivePhone.stopDtmf();
-    }
-
-    public void setRadioPower(boolean power) {
-        mActivePhone.setRadioPower(power);
-    }
-
-    public boolean getMessageWaitingIndicator() {
-        return mActivePhone.getMessageWaitingIndicator();
-    }
-
-    public boolean getCallForwardingIndicator() {
-        return mActivePhone.getCallForwardingIndicator();
-    }
-
-    public String getLine1Number() {
-        return mActivePhone.getLine1Number();
-    }
-
-    public String getCdmaMin() {
-        return mActivePhone.getCdmaMin();
-    }
-
-    public boolean isMinInfoReady() {
-        return mActivePhone.isMinInfoReady();
-    }
-
-    public String getCdmaPrlVersion() {
-        return mActivePhone.getCdmaPrlVersion();
-    }
-
-    public String getLine1AlphaTag() {
-        return mActivePhone.getLine1AlphaTag();
-    }
-
-    public void setLine1Number(String alphaTag, String number, Message onComplete) {
-        mActivePhone.setLine1Number(alphaTag, number, onComplete);
-    }
-
-    public String getVoiceMailNumber() {
-        return mActivePhone.getVoiceMailNumber();
-    }
-
-     /** @hide */
-    public int getVoiceMessageCount(){
-        return mActivePhone.getVoiceMessageCount();
-    }
-
-    public String getVoiceMailAlphaTag() {
-        return mActivePhone.getVoiceMailAlphaTag();
-    }
-
-    public void setVoiceMailNumber(String alphaTag,String voiceMailNumber,
-            Message onComplete) {
-        mActivePhone.setVoiceMailNumber(alphaTag, voiceMailNumber, onComplete);
-    }
-
-    public void getCallForwardingOption(int commandInterfaceCFReason,
-            Message onComplete) {
-        mActivePhone.getCallForwardingOption(commandInterfaceCFReason,
-                onComplete);
-    }
-
-    public void setCallForwardingOption(int commandInterfaceCFReason,
-            int commandInterfaceCFAction, String dialingNumber,
-            int timerSeconds, Message onComplete) {
-        mActivePhone.setCallForwardingOption(commandInterfaceCFReason,
-            commandInterfaceCFAction, dialingNumber, timerSeconds, onComplete);
-    }
-
-    public void getOutgoingCallerIdDisplay(Message onComplete) {
-        mActivePhone.getOutgoingCallerIdDisplay(onComplete);
-    }
-
-    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
-            Message onComplete) {
-        mActivePhone.setOutgoingCallerIdDisplay(commandInterfaceCLIRMode,
-                onComplete);
-    }
-
-    public void getCallWaiting(Message onComplete) {
-        mActivePhone.getCallWaiting(onComplete);
-    }
-
-    public void setCallWaiting(boolean enable, Message onComplete) {
-        mActivePhone.setCallWaiting(enable, onComplete);
-    }
-
-    public void getAvailableNetworks(Message response) {
-        mActivePhone.getAvailableNetworks(response);
-    }
-
-    public void setNetworkSelectionModeAutomatic(Message response) {
-        mActivePhone.setNetworkSelectionModeAutomatic(response);
-    }
-
-    public void selectNetworkManually(NetworkInfo network, Message response) {
-        mActivePhone.selectNetworkManually(network, response);
-    }
-
-    public void setPreferredNetworkType(int networkType, Message response) {
-        mActivePhone.setPreferredNetworkType(networkType, response);
-    }
-
-    public void getPreferredNetworkType(Message response) {
-        mActivePhone.getPreferredNetworkType(response);
-    }
-
-    public void getNeighboringCids(Message response) {
-        mActivePhone.getNeighboringCids(response);
-    }
-
-    public void setOnPostDialCharacter(Handler h, int what, Object obj) {
-        mActivePhone.setOnPostDialCharacter(h, what, obj);
-    }
-
-    public void setMute(boolean muted) {
-        mActivePhone.setMute(muted);
-    }
-
-    public boolean getMute() {
-        return mActivePhone.getMute();
-    }
-
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        mActivePhone.invokeOemRilRequestRaw(data, response);
-    }
-
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-        mActivePhone.invokeOemRilRequestStrings(strings, response);
-    }
-
-    public void getDataCallList(Message response) {
-        mActivePhone.getDataCallList(response);
-    }
-
-    public List<DataConnection> getCurrentDataConnectionList() {
-        return mActivePhone.getCurrentDataConnectionList();
-    }
-
-    public void updateServiceLocation() {
-        mActivePhone.updateServiceLocation();
-    }
-
-    public void enableLocationUpdates() {
-        mActivePhone.enableLocationUpdates();
-    }
-
-    public void disableLocationUpdates() {
-        mActivePhone.disableLocationUpdates();
-    }
-
-    public void setUnitTestMode(boolean f) {
-        mActivePhone.setUnitTestMode(f);
-    }
-
-    public boolean getUnitTestMode() {
-        return mActivePhone.getUnitTestMode();
-    }
-
-    public void setBandMode(int bandMode, Message response) {
-        mActivePhone.setBandMode(bandMode, response);
-    }
-
-    public void queryAvailableBandMode(Message response) {
-        mActivePhone.queryAvailableBandMode(response);
-    }
-
-    public boolean getDataRoamingEnabled() {
-        return mActivePhone.getDataRoamingEnabled();
-    }
-
-    public void setDataRoamingEnabled(boolean enable) {
-        mActivePhone.setDataRoamingEnabled(enable);
-    }
-
-    public void queryCdmaRoamingPreference(Message response) {
-        mActivePhone.queryCdmaRoamingPreference(response);
-    }
-
-    public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
-        mActivePhone.setCdmaRoamingPreference(cdmaRoamingType, response);
-    }
-
-    public void setCdmaSubscription(int cdmaSubscriptionType, Message response) {
-        mActivePhone.setCdmaSubscription(cdmaSubscriptionType, response);
-    }
-
-    public SimulatedRadioControl getSimulatedRadioControl() {
-        return mActivePhone.getSimulatedRadioControl();
-    }
-
-    public boolean enableDataConnectivity() {
-        return mActivePhone.enableDataConnectivity();
-    }
-
-    public boolean disableDataConnectivity() {
-        return mActivePhone.disableDataConnectivity();
-    }
-
-    public int enableApnType(String type) {
-        return mActivePhone.enableApnType(type);
-    }
-
-    public int disableApnType(String type) {
-        return mActivePhone.disableApnType(type);
-    }
-
-    public boolean isDataConnectivityEnabled() {
-        return mActivePhone.isDataConnectivityEnabled();
-    }
-
-    public boolean isDataConnectivityPossible() {
-        return mActivePhone.isDataConnectivityPossible();
-    }
-
-    public String getInterfaceName(String apnType) {
-        return mActivePhone.getInterfaceName(apnType);
-    }
-
-    public String getIpAddress(String apnType) {
-        return mActivePhone.getIpAddress(apnType);
-    }
-
-    public String getGateway(String apnType) {
-        return mActivePhone.getGateway(apnType);
-    }
-
-    public String[] getDnsServers(String apnType) {
-        return mActivePhone.getDnsServers(apnType);
-    }
-
-    public String getDeviceId() {
-        return mActivePhone.getDeviceId();
-    }
-
-    public String getDeviceSvn() {
-        return mActivePhone.getDeviceSvn();
-    }
-
-    public String getSubscriberId() {
-        return mActivePhone.getSubscriberId();
-    }
-
-    public String getIccSerialNumber() {
-        return mActivePhone.getIccSerialNumber();
-    }
-
-    public String getEsn() {
-        return mActivePhone.getEsn();
-    }
-
-    public String getMeid() {
-        return mActivePhone.getMeid();
-    }
-
-    public PhoneSubInfo getPhoneSubInfo(){
-        return mActivePhone.getPhoneSubInfo();
-    }
-
-    public IccSmsInterfaceManager getIccSmsInterfaceManager(){
-        return mActivePhone.getIccSmsInterfaceManager();
-    }
-
-    public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
-        return mActivePhone.getIccPhoneBookInterfaceManager();
-    }
-
-    public void setTTYMode(int ttyMode, Message onComplete) {
-        mActivePhone.setTTYMode(ttyMode, onComplete);
-    }
-
-    public void queryTTYMode(Message onComplete) {
-        mActivePhone.queryTTYMode(onComplete);
-    }
-
-    public void activateCellBroadcastSms(int activate, Message response) {
-        mActivePhone.activateCellBroadcastSms(activate, response);
-    }
-
-    public void getCellBroadcastSmsConfig(Message response) {
-        mActivePhone.getCellBroadcastSmsConfig(response);
-    }
-
-    public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
-        mActivePhone.setCellBroadcastSmsConfig(configValuesArray, response);
-    }
-
-    public void notifyDataActivity() {
-         mActivePhone.notifyDataActivity();
-    }
-
-    public void getSmscAddress(Message result) {
-        mActivePhone.getSmscAddress(result);
-    }
-
-    public void setSmscAddress(String address, Message result) {
-        mActivePhone.setSmscAddress(address, result);
-    }
-
-    public int getCdmaEriIconIndex() {
-         return mActivePhone.getCdmaEriIconIndex();
-    }
-
-     public String getCdmaEriText() {
-         return mActivePhone.getCdmaEriText();
-     }
-
-    public int getCdmaEriIconMode() {
-         return mActivePhone.getCdmaEriIconMode();
-    }
-
-    public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete){
-        mActivePhone.sendBurstDtmf(dtmfString, on, off, onComplete);
-    }
-
-    public void exitEmergencyCallbackMode(){
-        mActivePhone.exitEmergencyCallbackMode();
-    }
-
-    public boolean isOtaSpNumber(String dialStr){
-        return mActivePhone.isOtaSpNumber(dialStr);
-    }
-
-    public void registerForCallWaiting(Handler h, int what, Object obj){
-        mActivePhone.registerForCallWaiting(h,what,obj);
-    }
-
-    public void unregisterForCallWaiting(Handler h){
-        mActivePhone.unregisterForCallWaiting(h);
-    }
-
-    public void registerForSignalInfo(Handler h, int what, Object obj) {
-        mActivePhone.registerForSignalInfo(h,what,obj);
-    }
-
-    public void unregisterForSignalInfo(Handler h) {
-        mActivePhone.unregisterForSignalInfo(h);
-    }
-
-    public void registerForDisplayInfo(Handler h, int what, Object obj) {
-        mActivePhone.registerForDisplayInfo(h,what,obj);
-    }
-
-    public void unregisterForDisplayInfo(Handler h) {
-        mActivePhone.unregisterForDisplayInfo(h);
-    }
-
-    public void registerForNumberInfo(Handler h, int what, Object obj) {
-        mActivePhone.registerForNumberInfo(h, what, obj);
-    }
-
-    public void unregisterForNumberInfo(Handler h) {
-        mActivePhone.unregisterForNumberInfo(h);
-    }
-
-    public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) {
-        mActivePhone.registerForRedirectedNumberInfo(h, what, obj);
-    }
-
-    public void unregisterForRedirectedNumberInfo(Handler h) {
-        mActivePhone.unregisterForRedirectedNumberInfo(h);
-    }
-
-    public void registerForLineControlInfo(Handler h, int what, Object obj) {
-        mActivePhone.registerForLineControlInfo( h, what, obj);
-    }
-
-    public void unregisterForLineControlInfo(Handler h) {
-        mActivePhone.unregisterForLineControlInfo(h);
-    }
-
-    public void registerFoT53ClirlInfo(Handler h, int what, Object obj) {
-        mActivePhone.registerFoT53ClirlInfo(h, what, obj);
-    }
-
-    public void unregisterForT53ClirInfo(Handler h) {
-        mActivePhone.unregisterForT53ClirInfo(h);
-    }
-
-    public void registerForT53AudioControlInfo(Handler h, int what, Object obj) {
-        mActivePhone.registerForT53AudioControlInfo( h, what, obj);
-    }
-
-    public void unregisterForT53AudioControlInfo(Handler h) {
-        mActivePhone.unregisterForT53AudioControlInfo(h);
-    }
-
-    public void setOnEcbModeExitResponse(Handler h, int what, Object obj){
-        mActivePhone.setOnEcbModeExitResponse(h,what,obj);
-    }
-
-    public void unsetOnEcbModeExitResponse(Handler h){
-        mActivePhone.unsetOnEcbModeExitResponse(h);
-    }
-}
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
new file mode 100644
index 0000000..b02c1cb
--- /dev/null
+++ b/tools/obbtool/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright 2010 The Android Open Source Project
+#
+# Opaque Binary Blob (OBB) Tool
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	Main.cpp
+
+#LOCAL_C_INCLUDES +=
+
+LOCAL_STATIC_LIBRARIES := \
+	libutils \
+	libcutils
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -lpthread
+endif
+
+LOCAL_MODULE := obbtool
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # TARGET_BUILD_APPS
diff --git a/tools/obbtool/Main.cpp b/tools/obbtool/Main.cpp
new file mode 100644
index 0000000..2a9bf04
--- /dev/null
+++ b/tools/obbtool/Main.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+#include <utils/ObbFile.h>
+#include <utils/String8.h>
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+using namespace android;
+
+static const char* gProgName = "obbtool";
+static const char* gProgVersion = "1.0";
+
+static int wantUsage = 0;
+static int wantVersion = 0;
+
+#define ADD_OPTS "n:v:f:c:"
+static const struct option longopts[] = {
+    {"help",       no_argument, &wantUsage,   1},
+    {"version",    no_argument, &wantVersion, 1},
+
+    /* Args for "add" */
+    {"name",       required_argument, NULL, 'n'},
+    {"version",    required_argument, NULL, 'v'},
+    {"filesystem", required_argument, NULL, 'f'},
+    {"crypto",     required_argument, NULL, 'c'},
+
+    {NULL, 0, NULL, '\0'}
+};
+
+struct package_info_t {
+    char* packageName;
+    int packageVersion;
+    char* filesystem;
+    char* crypto;
+};
+
+/*
+ * Print usage info.
+ */
+void usage(void)
+{
+    fprintf(stderr, "Opaque Binary Blob (OBB) Tool\n\n");
+    fprintf(stderr, "Usage:\n");
+    fprintf(stderr,
+        " %s a[dd] [ OPTIONS ] FILENAME\n"
+        "   Adds an OBB signature to the file.\n\n", gProgName);
+    fprintf(stderr,
+        " %s r[emove] FILENAME\n"
+        "   Removes the OBB signature from the file.\n\n", gProgName);
+    fprintf(stderr,
+        " %s i[nfo] FILENAME\n"
+        "   Prints the OBB signature information of a file.\n\n", gProgName);
+}
+
+void doAdd(const char* filename, struct package_info_t* info) {
+    ObbFile *obb = new ObbFile();
+    if (obb->readFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: OBB signature already present\n", filename);
+        return;
+    }
+
+    obb->setPackageName(String8(info->packageName));
+    obb->setVersion(info->packageVersion);
+
+    if (!obb->writeTo(filename)) {
+        fprintf(stderr, "ERROR: %s: couldn't write OBB signature: %s\n",
+                filename, strerror(errno));
+        return;
+    }
+
+    fprintf(stderr, "OBB signature successfully written\n");
+}
+
+void doRemove(const char* filename) {
+    ObbFile *obb = new ObbFile();
+    if (!obb->readFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: no OBB signature present\n", filename);
+        return;
+    }
+
+    if (!obb->removeFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: couldn't remove OBB signature\n", filename);
+        return;
+    }
+
+    fprintf(stderr, "OBB signature successfully removed\n");
+}
+
+void doInfo(const char* filename) {
+    ObbFile *obb = new ObbFile();
+    if (!obb->readFrom(filename)) {
+        fprintf(stderr, "ERROR: %s: couldn't read OBB signature\n", filename);
+        return;
+    }
+
+    printf("OBB info for '%s':\n", filename);
+    printf("Package name: %s\n", obb->getPackageName().string());
+    printf("     Version: %d\n", obb->getVersion());
+}
+
+/*
+ * Parse args.
+ */
+int main(int argc, char* const argv[])
+{
+    const char *prog = argv[0];
+    struct options *options;
+    int opt;
+    int option_index = 0;
+    struct package_info_t package_info;
+
+    int result = 1;    // pessimistically assume an error.
+
+    if (argc < 2) {
+        wantUsage = 1;
+        goto bail;
+    }
+
+    while ((opt = getopt_long(argc, argv, ADD_OPTS, longopts, &option_index)) != -1) {
+        switch (opt) {
+        case 0:
+            if (longopts[option_index].flag)
+                break;
+            fprintf(stderr, "'%s' requires an argument\n", longopts[option_index].name);
+            wantUsage = 1;
+            goto bail;
+        case 'n':
+            package_info.packageName = optarg;
+            break;
+        case 'v':
+            char *end;
+            package_info.packageVersion = strtol(optarg, &end, 10);
+            if (*optarg == '\0' || *end != '\0') {
+                fprintf(stderr, "ERROR: invalid version; should be integer!\n\n");
+                wantUsage = 1;
+                goto bail;
+            }
+            break;
+        case 'f':
+            package_info.filesystem = optarg;
+            break;
+        case 'c':
+            package_info.crypto = optarg;
+            break;
+        case '?':
+            wantUsage = 1;
+            goto bail;
+        }
+    }
+
+    if (wantVersion) {
+        fprintf(stderr, "%s %s\n", gProgName, gProgVersion);
+    }
+
+    if (wantUsage) {
+        goto bail;
+    }
+
+#define CHECK_OP(name) \
+    if (strncmp(op, name, opsize)) { \
+        fprintf(stderr, "ERROR: unknown function '%s'!\n\n", op); \
+        wantUsage = 1; \
+        goto bail; \
+    }
+
+    if (optind < argc) {
+        const char* op = argv[optind++];
+        const int opsize = strlen(op);
+
+        if (optind >= argc) {
+            fprintf(stderr, "ERROR: filename required!\n\n");
+            wantUsage = 1;
+            goto bail;
+        }
+
+        const char* filename = argv[optind++];
+
+        switch (op[0]) {
+        case 'a':
+            CHECK_OP("add");
+            if (package_info.packageName == NULL) {
+                fprintf(stderr, "ERROR: arguments required 'packageName' and 'version'\n");
+                goto bail;
+            }
+            doAdd(filename, &package_info);
+            break;
+        case 'r':
+            CHECK_OP("remove");
+            doRemove(filename);
+            break;
+        case 'i':
+            CHECK_OP("info");
+            doInfo(filename);
+            break;
+        default:
+            fprintf(stderr, "ERROR: unknown command '%s'!\n\n", op);
+            wantUsage = 1;
+            goto bail;
+        }
+    }
+
+bail:
+    if (wantUsage) {
+        usage();
+        result = 2;
+    }
+
+    return result;
+}