Merge "Remove old location classes." into gingerbread
diff --git a/api/current.xml b/api/current.xml
index 179c515..a37a533 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -52619,6 +52619,118 @@
 >
 </field>
 </class>
+<class name="ObbInfo"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="parcelableFlags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OBB_OVERLAY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="flags"
+ type="int"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="packageName"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="version"
+ type="int"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="ObbScanner"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getObbInfo"
+ return="android.content.res.ObbInfo"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="filePath" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
 <class name="Resources"
  extends="java.lang.Object"
  abstract="false"
@@ -129032,6 +129144,38 @@
 </package>
 <package name="android.os.storage"
 >
+<class name="OnObbStateChangeListener"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="OnObbStateChangeListener"
+ type="android.os.storage.OnObbStateChangeListener"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onObbStateChange"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="state" type="java.lang.String">
+</parameter>
+</method>
+</class>
 <class name="StorageManager"
  extends="java.lang.Object"
  abstract="false"
@@ -129082,6 +129226,8 @@
 </parameter>
 <parameter name="key" type="java.lang.String">
 </parameter>
+<parameter name="listener" type="android.os.storage.OnObbStateChangeListener">
+</parameter>
 </method>
 <method name="unmountObb"
  return="boolean"
@@ -129097,6 +129243,8 @@
 </parameter>
 <parameter name="force" type="boolean">
 </parameter>
+<parameter name="listener" type="android.os.storage.OnObbStateChangeListener">
+</parameter>
 <exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
 </exception>
 </method>
diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java
index 838c5ff..7b962e5 100644
--- a/core/java/android/content/res/ObbInfo.java
+++ b/core/java/android/content/res/ObbInfo.java
@@ -20,9 +20,9 @@
 import android.os.Parcelable;
 
 /**
- * Basic information about a Opaque Binary Blob (OBB) that reflects
- * the info from the footer on the OBB file.
- * @hide
+ * Basic information about a Opaque Binary Blob (OBB) that reflects the info
+ * from the footer on the OBB file. This information may be manipulated by a
+ * developer with the <code>obbtool</code> program in the Android SDK.
  */
 public class ObbInfo implements Parcelable {
     /** Flag noting that this OBB is an overlay patch for a base OBB. */
@@ -43,7 +43,8 @@
      */
     public int flags;
 
-    public ObbInfo() {
+    // Only allow things in this package to instantiate.
+    /* package */ ObbInfo() {
     }
 
     public String toString() {
diff --git a/core/java/android/content/res/ObbScanner.java b/core/java/android/content/res/ObbScanner.java
index eb383c3..a3f141e 100644
--- a/core/java/android/content/res/ObbScanner.java
+++ b/core/java/android/content/res/ObbScanner.java
@@ -16,25 +16,43 @@
 
 package android.content.res;
 
+import java.io.File;
+import java.io.IOException;
+
 /**
- * Class to scan Opaque Binary Blob (OBB) files.
- * @hide
+ * Class to scan Opaque Binary Blob (OBB) files. Use this to get information
+ * about an OBB file for use in a program via {@link ObbInfo}.
  */
 public class ObbScanner {
     // Don't allow others to instantiate this class
     private ObbScanner() {}
 
-    public static ObbInfo getObbInfo(String filePath) {
+    /**
+     * Scan a file for OBB information.
+     * 
+     * @param filePath path to the OBB file to be scanned.
+     * @return ObbInfo object information corresponding to the file path
+     * @throws IllegalArgumentException if the OBB file couldn't be found
+     * @throws IOException if the OBB file couldn't be read
+     */
+    public static ObbInfo getObbInfo(String filePath) throws IOException {
         if (filePath == null) {
-            return null;
+            throw new IllegalArgumentException("file path cannot be null");
         }
 
-        ObbInfo obbInfo = new ObbInfo();
-        if (!getObbInfo_native(filePath, obbInfo)) {
-            throw new IllegalArgumentException("Could not read OBB file: " + filePath);
+        final File obbFile = new File(filePath);
+        if (!obbFile.exists()) {
+            throw new IllegalArgumentException("OBB file does nto exist: " + filePath);
         }
+
+        final String canonicalFilePath = obbFile.getCanonicalPath();
+
+        ObbInfo obbInfo = new ObbInfo();
+        getObbInfo_native(canonicalFilePath, obbInfo);
+
         return obbInfo;
     }
 
-    private native static boolean getObbInfo_native(String filePath, ObbInfo obbInfo);
+    private native static void getObbInfo_native(String filePath, ObbInfo obbInfo)
+            throws IOException;
 }
diff --git a/core/java/android/os/storage/OnObbStateChangeListener.java b/core/java/android/os/storage/OnObbStateChangeListener.java
new file mode 100644
index 0000000..a2d0a56
--- /dev/null
+++ b/core/java/android/os/storage/OnObbStateChangeListener.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Used for receiving notifications from {@link StorageManager}.
+ */
+public abstract class OnObbStateChangeListener {
+    /**
+     * Called when an OBB has changed states.
+     * 
+     * @param path path to the OBB file the state change has happened on
+     * @param state the current state of the OBB
+     */
+    public void onObbStateChange(String path, String state) {
+    }
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index df0b69c..14da00a 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -23,13 +23,28 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
- * StorageManager is the interface to the systems storage service.
+ * StorageManager is the interface to the systems storage service. The storage
+ * manager handles storage-related items such as Opaque Binary Blobs (OBBs).
+ * <p>
+ * OBBs contain a filesystem that maybe be encrypted on disk and mounted
+ * on-demand from an application. OBBs are a good way of providing large amounts
+ * of binary assets without packaging them into APKs as they may be multiple
+ * gigabytes in size. However, due to their size, they're most likely stored in
+ * a shared storage pool accessible from all programs. The system does not
+ * guarantee the security of the OBB file itself: if any program modifies the
+ * OBB, there is no guarantee that a read from that OBB will produce the
+ * expected output.
+ * <p>
  * Get an instance of this class by calling
- * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
- * of {@link android.content.Context#STORAGE_SERVICE}.
+ * {@link android.content.Context#getSystemService(java.lang.String)} with an
+ * argument of {@link android.content.Context#STORAGE_SERVICE}.
  */
 
 public class StorageManager
@@ -75,11 +90,113 @@
     /**
      * Binder listener for OBB action results.
      */
-    private final ObbActionBinderListener mObbActionListener = new ObbActionBinderListener();
-    private class ObbActionBinderListener extends IObbActionListener.Stub {
+    private final ObbActionListener mObbActionListener = new ObbActionListener();
+
+    private class ObbActionListener extends IObbActionListener.Stub {
+        private List<WeakReference<ObbListenerDelegate>> mListeners = new LinkedList<WeakReference<ObbListenerDelegate>>();
+
         @Override
         public void onObbResult(String filename, String status) throws RemoteException {
-            Log.i(TAG, "filename = " + filename + ", result = " + status);
+            synchronized (mListeners) {
+                final Iterator<WeakReference<ObbListenerDelegate>> iter = mListeners.iterator();
+                while (iter.hasNext()) {
+                    final WeakReference<ObbListenerDelegate> ref = iter.next();
+
+                    final ObbListenerDelegate delegate = (ref == null) ? null : ref.get();
+                    if (delegate == null) {
+                        iter.remove();
+                        continue;
+                    }
+
+                    delegate.sendObbStateChanged(filename, status);
+                }
+            }
+        }
+
+        public void addListener(OnObbStateChangeListener listener) {
+            if (listener == null) {
+                return;
+            }
+
+            synchronized (mListeners) {
+                final Iterator<WeakReference<ObbListenerDelegate>> iter = mListeners.iterator();
+                while (iter.hasNext()) {
+                    final WeakReference<ObbListenerDelegate> ref = iter.next();
+
+                    final ObbListenerDelegate delegate = (ref == null) ? null : ref.get();
+                    if (delegate == null) {
+                        iter.remove();
+                        continue;
+                    }
+
+                    /*
+                     * If we're already in the listeners, we don't need to be in
+                     * there again.
+                     */
+                    if (listener.equals(delegate.getListener())) {
+                        return;
+                    }
+                }
+
+                final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
+                mListeners.add(new WeakReference<ObbListenerDelegate>(delegate));
+            }
+        }
+    }
+
+    /**
+     * Private class containing sender and receiver code for StorageEvents.
+     */
+    private class ObbListenerDelegate {
+        private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
+        private final Handler mHandler;
+
+        ObbListenerDelegate(OnObbStateChangeListener listener) {
+            mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
+            mHandler = new Handler(mTgtLooper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    final OnObbStateChangeListener listener = getListener();
+                    if (listener == null) {
+                        return;
+                    }
+
+                    StorageEvent e = (StorageEvent) msg.obj;
+
+                    if (msg.what == StorageEvent.EVENT_OBB_STATE_CHANGED) {
+                        ObbStateChangedStorageEvent ev = (ObbStateChangedStorageEvent) e;
+                        listener.onObbStateChange(ev.path, ev.state);
+                    } else {
+                        Log.e(TAG, "Unsupported event " + msg.what);
+                    }
+                }
+            };
+        }
+
+        OnObbStateChangeListener getListener() {
+            if (mObbEventListenerRef == null) {
+                return null;
+            }
+            return mObbEventListenerRef.get();
+        }
+
+        void sendObbStateChanged(String path, String state) {
+            ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state);
+            mHandler.sendMessage(e.getMessage());
+        }
+    }
+
+    /**
+     * Message sent during an OBB status change event.
+     */
+    private class ObbStateChangedStorageEvent extends StorageEvent {
+        public final String path;
+        public final String state;
+
+        public ObbStateChangedStorageEvent(String path, String state) {
+            super(EVENT_OBB_STATE_CHANGED);
+            this.path = path;
+            this.state = state;
         }
     }
 
@@ -88,8 +205,9 @@
      * and the target looper handler.
      */
     private class StorageEvent {
-        public static final int EVENT_UMS_CONNECTION_CHANGED = 1;
-        public static final int EVENT_STORAGE_STATE_CHANGED   = 2;
+        static final int EVENT_UMS_CONNECTION_CHANGED = 1;
+        static final int EVENT_STORAGE_STATE_CHANGED = 2;
+        static final int EVENT_OBB_STATE_CHANGED = 3;
 
         private Message mMessage;
 
@@ -300,19 +418,27 @@
      * specified, it is supplied to the mounting process to be used in any
      * encryption used in the OBB.
      * <p>
+     * The OBB will remain mounted for as long as the StorageManager reference
+     * is held by the application. As soon as this reference is lost, the OBBs
+     * in use will be unmounted. The {@link OnObbStateChangeListener} registered with
+     * this call will receive all further OBB-related events until it goes out
+     * of scope. If the caller is not interested in whether the call succeeds,
+     * the <code>listener</code> may be specified as <code>null</code>.
+     * <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
+     * That is, shared UID applications can attempt to mount any other
      * application's OBB that shares its UID.
-     * <p>
-     * STOPSHIP document more; discuss lack of guarantees of security
      * 
      * @param filename the path to the OBB file
-     * @param key decryption key
+     * @param key secret used to encrypt the OBB; may be <code>null</code> if no
+     *            encryption was used on the OBB.
      * @return whether the mount call was successfully queued or not
+     * @throws IllegalArgumentException when the OBB is already mounted
      */
-    public boolean mountObb(String filename, String key) {
+    public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
         try {
+            mObbActionListener.addListener(listener);
             mMountService.mountObb(filename, key, mObbActionListener);
             return true;
         } catch (RemoteException e) {
@@ -323,15 +449,20 @@
     }
 
     /**
-     * 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.
+     * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
+     * <code>force</code> flag is true, it will kill any application needed to
+     * unmount the given OBB (even the calling application).
+     * <p>
+     * The {@link OnObbStateChangeListener} registered with this call will receive all
+     * further OBB-related events until it goes out of scope. If the caller is
+     * not interested in whether the call succeeded, the listener may be
+     * specified as <code>null</code>.
      * <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.
      * <p>
-     * STOPSHIP document more; discuss lack of guarantees of security
      * 
      * @param filename path to the OBB file
      * @param force whether to kill any programs using this in order to unmount
@@ -339,8 +470,10 @@
      * @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) throws IllegalArgumentException {
+    public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener)
+            throws IllegalArgumentException {
         try {
+            mObbActionListener.addListener(listener);
             mMountService.unmountObb(filename, force, mObbActionListener);
             return true;
         } catch (RemoteException e) {
diff --git a/core/jni/android_content_res_ObbScanner.cpp b/core/jni/android_content_res_ObbScanner.cpp
index 62c89fc..2a9eacf 100644
--- a/core/jni/android_content_res_ObbScanner.cpp
+++ b/core/jni/android_content_res_ObbScanner.cpp
@@ -34,7 +34,17 @@
     jfieldID flags;
 } gObbInfoClassInfo;
 
-static jboolean android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject clazz, jstring file,
+static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+    jclass npeClazz;
+
+    npeClazz = env->FindClass(exc);
+    LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
+
+    env->ThrowNew(npeClazz, msg);
+}
+
+static void android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject clazz, jstring file,
         jobject obbInfo)
 {
     const char* filePath = env->GetStringUTFChars(file, JNI_FALSE);
@@ -42,7 +52,8 @@
     sp<ObbFile> obb = new ObbFile();
     if (!obb->readFrom(filePath)) {
         env->ReleaseStringUTFChars(file, filePath);
-        return JNI_FALSE;
+        doThrow(env, "java/io/IOException", "Could not read OBB file");
+        return;
     }
 
     env->ReleaseStringUTFChars(file, filePath);
@@ -51,13 +62,13 @@
 
     jstring packageName = env->NewStringUTF(packageNameStr);
     if (packageName == NULL) {
-        return JNI_FALSE;
+        doThrow(env, "java/io/IOException", "Could not read OBB file");
+        return;
     }
 
     env->SetObjectField(obbInfo, gObbInfoClassInfo.packageName, packageName);
     env->SetIntField(obbInfo, gObbInfoClassInfo.version, obb->getVersion());
-
-    return JNI_TRUE;
+    env->SetIntField(obbInfo, gObbInfoClassInfo.flags, obb->getFlags());
 }
 
 /*
@@ -65,7 +76,7 @@
  */
 static JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
-    { "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)Z",
+    { "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)V",
             (void*) android_content_res_ObbScanner_getObbInfo },
 };
 
diff --git a/include/storage/IMountService.h b/include/storage/IMountService.h
index a2735a4..436fc38 100644
--- a/include/storage/IMountService.h
+++ b/include/storage/IMountService.h
@@ -62,7 +62,8 @@
     virtual void finishMediaUpdate() = 0;
     virtual void mountObb(const String16& filename, const String16& key,
             const sp<IObbActionListener>& token) = 0;
-    virtual void unmountObb(const String16& filename, const bool force) = 0;
+    virtual void unmountObb(const String16& filename, const bool force,
+            const sp<IObbActionListener>& token) = 0;
     virtual bool isObbMounted(const String16& filename) = 0;
     virtual bool getMountedObbPath(const String16& filename, String16& path) = 0;
 };
diff --git a/libs/storage/IMountService.cpp b/libs/storage/IMountService.cpp
index 902bb27..3ad9319 100644
--- a/libs/storage/IMountService.cpp
+++ b/libs/storage/IMountService.cpp
@@ -429,8 +429,8 @@
         reply.readExceptionCode();
     }
 
-    void mountObb(const String16& filename, const String16& key, const sp<
-            IObbActionListener>& token)
+    void mountObb(const String16& filename, const String16& key,
+            const sp<IObbActionListener>& token)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IMountService::getInterfaceDescriptor());
@@ -448,7 +448,7 @@
         }
     }
 
-    void unmountObb(const String16& filename, const bool force)
+    void unmountObb(const String16& filename, const bool force, const sp<IObbActionListener>& token)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IMountService::getInterfaceDescriptor());
diff --git a/native/android/Android.mk b/native/android/Android.mk
index cc35a3a..44ec83f 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -12,6 +12,7 @@
     looper.cpp \
     native_activity.cpp \
     native_window.cpp \
+    obb.cpp \
     sensor.cpp \
     storage_manager.cpp
 
diff --git a/native/android/obb.cpp b/native/android/obb.cpp
new file mode 100644
index 0000000..e0cb1a6
--- /dev/null
+++ b/native/android/obb.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NObb"
+
+#include <android/obb.h>
+
+#include <utils/Log.h>
+#include <utils/ObbFile.h>
+
+using namespace android;
+
+struct AObbInfo : public ObbFile {};
+
+AObbInfo* AObbScanner_getObbInfo(const char* filename) {
+    AObbInfo* obbFile = new AObbInfo();
+    if (obbFile == NULL || !obbFile->readFrom(filename)) {
+        delete obbFile;
+        return NULL;
+    }
+    obbFile->incStrong((void*)AObbScanner_getObbInfo);
+    return static_cast<AObbInfo*>(obbFile);
+}
+
+void AObbInfo_delete(AObbInfo* obbInfo) {
+    if (obbInfo != NULL) {
+        obbInfo->decStrong((void*)AObbScanner_getObbInfo);
+    }
+}
+
+const char* AObbInfo_getPackageName(AObbInfo* obbInfo) {
+    return obbInfo->getPackageName();
+}
+
+int32_t AObbInfo_getVersion(AObbInfo* obbInfo) {
+    return obbInfo->getVersion();
+}
+
+int32_t AObbInfo_getFlags(AObbInfo* obbInfo) {
+    return obbInfo->getFlags();
+}
diff --git a/native/android/storage_manager.cpp b/native/android/storage_manager.cpp
index 6dbe746..2f20641 100644
--- a/native/android/storage_manager.cpp
+++ b/native/android/storage_manager.cpp
@@ -38,20 +38,20 @@
             mStorageManager(mgr)
     {}
 
-    virtual void onObbResult(const android::String16& filename, const android::String16& state) {
-        LOGD("Got obb result (%s, %s)\n", String8(filename).string(), String8(state).string());
-    }
+    virtual void onObbResult(const android::String16& filename, const android::String16& state);
 };
 
 struct AStorageManager : public RefBase {
 protected:
-    void* mObbCallback;
+    AStorageManager_obbCallbackFunc mObbCallback;
+    void* mObbCallbackData;
     sp<ObbActionListener> mObbActionListener;
     sp<IMountService> mMountService;
 
 public:
-    AStorageManager() :
-            mObbCallback(NULL)
+    AStorageManager()
+            : mObbCallback(NULL)
+            , mObbCallbackData(NULL)
     {
     }
 
@@ -73,8 +73,15 @@
         return true;
     }
 
-    void setObbCallback(void* cb) {
+    void setObbCallback(AStorageManager_obbCallbackFunc cb, void* data) {
         mObbCallback = cb;
+        mObbCallbackData = data;
+    }
+
+    void fireCallback(const char* filename, const char* state) {
+        if (mObbCallback != NULL) {
+            mObbCallback(filename, state, mObbCallbackData);
+        }
     }
 
     void mountObb(const char* filename, const char* key) {
@@ -85,7 +92,7 @@
 
     void unmountObb(const char* filename, const bool force) {
         String16 filename16(filename);
-        mMountService->unmountObb(filename16, force);
+        mMountService->unmountObb(filename16, force, mObbActionListener);
     }
 
     int isObbMounted(const char* filename) {
@@ -104,6 +111,10 @@
     }
 };
 
+void ObbActionListener::onObbResult(const android::String16& filename, const android::String16& state) {
+    mStorageManager->fireCallback(String8(filename).string(), String8(state).string());
+}
+
 
 AStorageManager* AStorageManager_new() {
     sp<AStorageManager> mgr = new AStorageManager();
@@ -120,8 +131,8 @@
     }
 }
 
-void AStorageManager_setObbCallback(AStorageManager* mgr, void* cb) {
-    mgr->setObbCallback(cb);
+void AStorageManager_setObbCallback(AStorageManager* mgr, AStorageManager_obbCallbackFunc cb, void* data) {
+    mgr->setObbCallback(cb, data);
 }
 
 void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key) {
diff --git a/native/include/android/obb.h b/native/include/android/obb.h
new file mode 100644
index 0000000..65e9b2a
--- /dev/null
+++ b/native/include/android/obb.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+
+#ifndef ANDROID_OBB_H
+#define ANDROID_OBB_H
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct AObbInfo;
+typedef struct AObbInfo AObbInfo;
+
+enum {
+    AOBBINFO_OVERLAY = 0x0001,
+};
+
+/**
+ * Scan an OBB and get information about it.
+ */
+AObbInfo* AObbScanner_getObbInfo(const char* filename);
+
+/**
+ * Destroy the AObbInfo object. You must call this when finished with the object.
+ */
+void AObbInfo_delete(AObbInfo* obbInfo);
+
+/**
+ * Get the package name for the OBB.
+ */
+const char* AObbInfo_getPackageName(AObbInfo* obbInfo);
+
+/**
+ * Get the version of an OBB file.
+ */
+int32_t AObbInfo_getVersion(AObbInfo* obbInfo);
+
+/**
+ * Get the flags of an OBB file.
+ */
+int32_t AObbInfo_getFlags(AObbInfo* obbInfo);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif      // ANDROID_OBB_H
diff --git a/native/include/android/storage_manager.h b/native/include/android/storage_manager.h
index bbed8a4..6f925c1 100644
--- a/native/include/android/storage_manager.h
+++ b/native/include/android/storage_manager.h
@@ -37,17 +37,22 @@
 void AStorageManager_delete(AStorageManager* mgr);
 
 /**
- * Callback to call when requested OBB is complete.
+ * Callback function for asynchronous calls made on OBB files.
  */
-void AStorageManager_setObbCallback(AStorageManager* mgr, void* cb);
+typedef void (*AStorageManager_obbCallbackFunc)(const char* filename, const char* state, void* data);
 
 /**
- * Attempts to mount an OBB file.
+ * Callback to call when requested asynchronous OBB operation is complete.
+ */
+void AStorageManager_setObbCallback(AStorageManager* mgr, AStorageManager_obbCallbackFunc cb, void* data);
+
+/**
+ * Attempts to mount an OBB file. This is an asynchronous operation.
  */
 void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key);
 
 /**
- * Attempts to unmount an OBB file.
+ * Attempts to unmount an OBB file. This is an asynchronous operation.
  */
 void AStorageManager_unmountObb(AStorageManager* mgr, const char* filename, const int force);
 
@@ -66,4 +71,4 @@
 };
 #endif
 
-#endif      // ANDROID_PACKAGE_MANAGER_H
+#endif      // ANDROID_STORAGE_MANAGER_H
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index f08bd3c..eb86277 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -156,7 +156,12 @@
         }
 
         public ObbInfo getObbInfo(String filename) {
-            return ObbScanner.getObbInfo(filename);
+            try {
+                return ObbScanner.getObbInfo(filename);
+            } catch (IOException e) {
+                Log.d(TAG, "Couldn't get OBB info", e);
+                return null;
+            }
         }
     };
 
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index a58e311..12d1d5c 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -71,6 +71,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:fadingEdge="none"
+            android:overscrollMode="ifContentScrolls"
             >
             <com.android.systemui.statusbar.NotificationLinearLayout
                 android:id="@+id/notificationLinearLayout"
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index cfba07a..f3625a8 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -46,6 +46,7 @@
 import android.os.storage.StorageResultCode;
 import android.util.Slog;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -148,7 +149,7 @@
      * Mounted OBB tracking information. Used to track the current state of all
      * OBBs.
      */
-    final private Map<IObbActionListener, ObbState> mObbMounts = new HashMap<IObbActionListener, ObbState>();
+    final private Map<IObbActionListener, List<ObbState>> mObbMounts = new HashMap<IObbActionListener, List<ObbState>>();
     final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
 
     class ObbState implements IBinder.DeathRecipient {
@@ -160,13 +161,13 @@
         }
 
         // OBB source filename
-        String filename;
+        final String filename;
 
         // Token of remote Binder caller
-        IObbActionListener token;
+        final IObbActionListener token;
 
         // Binder.callingUid()
-        public int callerUid;
+        final public int callerUid;
 
         // Whether this is mounted currently.
         boolean mounted;
@@ -225,9 +226,9 @@
     private static final int MAX_UNMOUNT_RETRIES = 4;
 
     class UnmountCallBack {
-        String path;
+        final String path;
+        final boolean force;
         int retries;
-        boolean force;
 
         UnmountCallBack(String path, boolean force) {
             retries = 0;
@@ -242,7 +243,7 @@
     }
 
     class UmsEnableCallBack extends UnmountCallBack {
-        String method;
+        final String method;
 
         UmsEnableCallBack(String path, String method, boolean force) {
             super(path, force);
@@ -1513,10 +1514,6 @@
                 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);
@@ -1554,14 +1551,25 @@
 
     private void addObbState(ObbState obbState) {
         synchronized (mObbMounts) {
-            mObbMounts.put(obbState.token, obbState);
+            List<ObbState> obbStates = mObbMounts.get(obbState.token);
+            if (obbStates == null) {
+                obbStates = new ArrayList<ObbState>();
+                mObbMounts.put(obbState.token, obbStates);
+            }
+            obbStates.add(obbState);
             mObbPathToStateMap.put(obbState.filename, obbState);
         }
     }
 
     private void removeObbState(ObbState obbState) {
         synchronized (mObbMounts) {
-            mObbMounts.remove(obbState.token);
+            final List<ObbState> obbStates = mObbMounts.get(obbState.token);
+            if (obbStates != null) {
+                obbStates.remove(obbState);
+            }
+            if (obbStates == null || obbStates.isEmpty()) {
+                mObbMounts.remove(obbState.token);
+            }
             mObbPathToStateMap.remove(obbState.filename);
         }
     }
@@ -1737,7 +1745,7 @@
             }
         }
 
-        abstract void handleExecute() throws RemoteException;
+        abstract void handleExecute() throws RemoteException, IOException;
         abstract void handleError();
     }
 
@@ -1749,8 +1757,12 @@
             mKey = key;
         }
 
-        public void handleExecute() throws RemoteException {
+        public void handleExecute() throws RemoteException, IOException {
             ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
+            if (obbInfo == null) {
+                throw new IOException("Couldn't read OBB file");
+            }
+
             if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
                 throw new IllegalArgumentException("Caller package does not match OBB file");
             }
@@ -1773,15 +1785,17 @@
 
             if (rc == StorageResultCode.OperationSucceeded) {
                 try {
-                    mObbState.token.onObbResult(mObbState.filename, "mounted");
+                    mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_MOUNTED);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
                 }
             } else {
-                Slog.e(TAG, "Couldn't mount OBB file");
+                Slog.e(TAG, "Couldn't mount OBB file: " + rc);
 
                 // We didn't succeed, so remove this from the mount-set.
                 removeObbState(mObbState);
+
+                mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
             }
         }
 
@@ -1789,7 +1803,7 @@
             removeObbState(mObbState);
 
             try {
-                mObbState.token.onObbResult(mObbState.filename, "error");
+                mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename);
             }
@@ -1818,8 +1832,11 @@
             mForceUnmount = force;
         }
 
-        public void handleExecute() throws RemoteException {
+        public void handleExecute() throws RemoteException, IOException {
             ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
+            if (obbInfo == null) {
+                throw new IOException("Couldn't read OBB file");
+            }
 
             if (!isCallerOwnerOfPackageOrSystem(obbInfo.packageName)) {
                 throw new IllegalArgumentException("Caller package does not match OBB file");
@@ -1843,13 +1860,13 @@
                 removeObbState(mObbState);
 
                 try {
-                    mObbState.token.onObbResult(mObbState.filename, "unmounted");
+                    mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_UNMOUNTED);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
                 }
             } else {
                 try {
-                    mObbState.token.onObbResult(mObbState.filename, "error");
+                    mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
                 }
@@ -1860,7 +1877,7 @@
             removeObbState(mObbState);
 
             try {
-                mObbState.token.onObbResult(mObbState.filename, "error");
+                mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename);
             }
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 5d32b74..71105f1 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -2105,6 +2105,7 @@
     }
 
     public void userActivity(long time, boolean noChangeLights) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
         userActivity(time, -1, noChangeLights, OTHER_EVENT, false);
     }
 
@@ -2128,7 +2129,6 @@
 
     private void userActivity(long time, long timeoutOverride, boolean noChangeLights,
             int eventType, boolean force) {
-        //mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
 
         if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0)
                 && (eventType == CHEEK_EVENT || eventType == TOUCH_EVENT)) {