Add OnLoadCompleteListener to SoundPool.
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 3803d9d..df21aa4 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -26,6 +26,10 @@
 import android.content.res.AssetFileDescriptor;
 import java.io.IOException;
 
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
 /**
  * The SoundPool class manages and plays audio resources for applications.
  *
@@ -103,9 +107,20 @@
     static { System.loadLibrary("soundpool"); }
 
     private final static String TAG = "SoundPool";
+    private final static boolean DEBUG = false;
 
     private int mNativeContext; // accessed by native methods
 
+    private EventHandler mEventHandler;
+    private OnLoadCompleteListener mOnLoadCompleteListener;
+
+    private final Object mLock;
+
+    // SoundPool messages
+    //
+    // must match SoundPool.h
+    private static final int SAMPLE_LOADED = 1;
+
     /**
      * Constructor. Constructs a SoundPool object with the following
      * characteristics:
@@ -120,7 +135,23 @@
      * @return a SoundPool object, or null if creation failed
      */
     public SoundPool(int maxStreams, int streamType, int srcQuality) {
-        native_setup(new WeakReference<SoundPool>(this), maxStreams, streamType, srcQuality);
+
+        // do native setup
+        if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) {
+            throw new RuntimeException("Native setup failed");
+        }
+        mLock = new Object();
+
+        // setup message handler
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+        }
+
     }
 
     /**
@@ -145,12 +176,11 @@
                 ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
                 if (fd != null) {
                     id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
-                    //Log.v(TAG, "close fd");
                     fd.close();
                 }
             }
         } catch (java.io.IOException e) {
-            Log.d(TAG, "error loading " + path);
+            Log.e(TAG, "error loading " + path);
         }
         return id;
     }
@@ -176,7 +206,6 @@
         if (afd != null) {
             id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
             try {
-                //Log.v(TAG, "close fd");
                 afd.close();
             } catch (java.io.IOException ex) {
                 //Log.d(TAG, "close failed:", ex);
@@ -359,6 +388,76 @@
     public native final void setRate(int streamID, float rate);
 
     /**
+     * Interface definition for a callback to be invoked when all the
+     * sounds are loaded.
+     *
+     * @hide
+     */
+    public interface OnLoadCompleteListener
+    {
+        /**
+         * Called when a sound has completed loading.
+         *
+         * @param soundPool SoundPool object from the load() method
+         * @param soundPool the sample ID of the sound loaded.
+         * @param status the status of the load operation (0 = success)
+         */
+        public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
+    }
+
+    /**
+     * Sets the callback hook for the OnLoadCompleteListener.
+     *
+     * @hide
+     */
+    public void setOnLoadCompleteListener(OnLoadCompleteListener listener)
+    {
+        synchronized(mLock) {
+            mOnLoadCompleteListener = listener;
+        }
+    }
+
+    private class EventHandler extends Handler
+    {
+        private SoundPool mSoundPool;
+
+        public EventHandler(SoundPool soundPool, Looper looper) {
+            super(looper);
+            mSoundPool = soundPool;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+            case SAMPLE_LOADED:
+                if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
+                synchronized(mLock) {
+                    if (mOnLoadCompleteListener != null) {
+                        mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2);
+                    }
+                }
+                break;
+            default:
+                Log.e(TAG, "Unknown message type " + msg.what);
+                return;
+            }
+        }
+    }
+
+    // post event from native code to message handler
+    private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj)
+    {
+        SoundPool soundPool = (SoundPool)((WeakReference)weakRef).get();
+        if (soundPool == null)
+            return;
+
+        if (soundPool.mEventHandler != null) {
+            Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
+            soundPool.mEventHandler.sendMessage(m);
+        }
+    }
+
+    /**
      * Release the SoundPool resources.
      *
      * Release all memory and native resources used by the SoundPool
@@ -367,8 +466,7 @@
      */
     public native final void release();
 
-    private native final void native_setup(Object mediaplayer_this,
-            int maxStreams, int streamType, int srcQuality);
+    private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality);
 
     protected void finalize() { release(); }
 }
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 70fba7e..e57f4a4 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -61,6 +61,9 @@
     mNextSampleID = 0;
     mNextChannelID = 0;
 
+    mCallback = 0;
+    mUserData = 0;
+
     mChannelPool = new SoundChannel[mMaxChannels];
     for (int i = 0; i < mMaxChannels; ++i) {
         mChannelPool[i].init(this);
@@ -141,7 +144,7 @@
 
 bool SoundPool::startThreads()
 {
-    createThread(beginThread, this);
+    createThreadEtc(beginThread, this, "SoundPoolThread");
     if (mDecodeThread == NULL)
         mDecodeThread = new SoundPoolThread(this);
     return mDecodeThread != NULL;
@@ -372,6 +375,21 @@
     }
 }
 
+void SoundPool::setCallback(SoundPoolCallback* callback, void* user)
+{
+    Mutex::Autolock lock(&mCallbackLock);
+    mCallback = callback;
+    mUserData = user;
+}
+
+void SoundPool::notify(SoundPoolEvent event)
+{
+    Mutex::Autolock lock(&mCallbackLock);
+    if (mCallback != NULL) {
+        mCallback(event, this, mUserData);
+    }
+}
+
 void SoundPool::dump()
 {
     for (int i = 0; i < mMaxChannels; ++i) {
@@ -422,7 +440,7 @@
     delete mUrl;
 }
 
-void Sample::doLoad()
+status_t Sample::doLoad()
 {
     uint32_t sampleRate;
     int numChannels;
@@ -439,19 +457,19 @@
     }
     if (p == 0) {
         LOGE("Unable to load sample: %s", mUrl);
-        return;
+        return -1;
     }
     LOGV("pointer = %p, size = %u, sampleRate = %u, numChannels = %d",
             p->pointer(), p->size(), sampleRate, numChannels);
 
     if (sampleRate > kMaxSampleRate) {
        LOGE("Sample rate (%u) out of range", sampleRate);
-       return;
+       return - 1;
     }
 
     if ((numChannels < 1) || (numChannels > 2)) {
         LOGE("Sample channel count (%d) out of range", numChannels);
-        return;
+        return - 1;
     }
 
     //_dumpBuffer(p->pointer(), p->size());
@@ -464,6 +482,7 @@
     mNumChannels = numChannels;
     mFormat = format;
     mState = READY;
+    return 0;
 }
 
 
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 94cd978..7a2d631 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -24,8 +24,6 @@
 #include <media/AudioTrack.h>
 #include <cutils/atomic.h>
 
-#include <nativehelper/jni.h>
-
 namespace android {
 
 static const int IDLE_PRIORITY = -1;
@@ -43,10 +41,11 @@
     int         mMsg;
     int         mArg1;
     int         mArg2;
+    enum MessageType { INVALID, SAMPLE_LOADED };
 };
 
-// JNI for calling back Java SoundPool object
-extern void android_soundpool_SoundPool_notify(jobject ref, const SoundPoolEvent *event);
+// callback function prototype
+typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
 
 // tracks samples used by application
 class Sample  : public RefBase {
@@ -62,7 +61,7 @@
     size_t size() { return mSize; }
     int state() { return mState; }
     uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); }
-    void doLoad();
+    status_t doLoad();
     void startLoad() { mState = LOADING; }
     sp<IMemory> getIMemory() { return mData; }
 
@@ -182,6 +181,10 @@
     // called from AudioTrack thread
     void done(SoundChannel* channel);
 
+    // callback function
+    void setCallback(SoundPoolCallback* callback, void* user);
+    void* getUserData() { return mUserData; }
+
 private:
     SoundPool() {} // no default constructor
     bool startThreads();
@@ -191,6 +194,7 @@
     SoundChannel* findNextChannel (int channelID);
     SoundChannel* allocateChannel(int priority);
     void moveToFront(SoundChannel* channel);
+    void notify(SoundPoolEvent event);
     void dump();
 
     // restart thread
@@ -214,6 +218,11 @@
     int                     mNextSampleID;
     int                     mNextChannelID;
     bool                    mQuit;
+
+    // callback
+    Mutex                   mCallbackLock;
+    SoundPoolCallback*      mCallback;
+    void*                   mUserData;
 };
 
 } // end namespace android
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp
index 4e6798d..e32c794 100644
--- a/media/jni/soundpool/SoundPoolThread.cpp
+++ b/media/jni/soundpool/SoundPoolThread.cpp
@@ -22,50 +22,54 @@
 
 namespace android {
 
-void SoundPoolThread::MessageQueue::write(SoundPoolMsg msg) {
-    LOGV("MessageQueue::write - acquiring lock\n");
+void SoundPoolThread::write(SoundPoolMsg msg) {
     Mutex::Autolock lock(&mLock);
-    while (mQueue.size() >= maxMessages) {
-        LOGV("MessageQueue::write - wait\n");
+    while (mMsgQueue.size() >= maxMessages) {
         mCondition.wait(mLock);
     }
-    LOGV("MessageQueue::write - push message\n");
-    mQueue.push(msg);
-    mCondition.signal();
+
+    // if thread is quitting, don't add to queue
+    if (mRunning) {
+        mMsgQueue.push(msg);
+        mCondition.signal();
+    }
 }
 
-const SoundPoolMsg SoundPoolThread::MessageQueue::read() {
-    LOGV("MessageQueue::read - acquiring lock\n");
+const SoundPoolMsg SoundPoolThread::read() {
     Mutex::Autolock lock(&mLock);
-    while (mQueue.size() == 0) {
-        LOGV("MessageQueue::read - wait\n");
+    while (mMsgQueue.size() == 0) {
         mCondition.wait(mLock);
     }
-    SoundPoolMsg msg = mQueue[0];
-    LOGV("MessageQueue::read - retrieve message\n");
-    mQueue.removeAt(0);
+    SoundPoolMsg msg = mMsgQueue[0];
+    mMsgQueue.removeAt(0);
     mCondition.signal();
     return msg;
 }
 
-void SoundPoolThread::MessageQueue::quit() {
+void SoundPoolThread::quit() {
     Mutex::Autolock lock(&mLock);
-    mQueue.clear();
-    mQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
-    mCondition.signal();
-    mCondition.wait(mLock);
+    if (mRunning) {
+        mRunning = false;
+        mMsgQueue.clear();
+        mMsgQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
+        mCondition.signal();
+        mCondition.wait(mLock);
+    }
     LOGV("return from quit");
 }
 
 SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
     mSoundPool(soundPool)
 {
-    mMessages.setCapacity(maxMessages);
-    createThread(beginThread, this);
+    mMsgQueue.setCapacity(maxMessages);
+    if (createThread(beginThread, this)) {
+        mRunning = true;
+    }
 }
 
 SoundPoolThread::~SoundPoolThread()
 {
+    quit();
 }
 
 int SoundPoolThread::beginThread(void* arg) {
@@ -77,7 +81,7 @@
 int SoundPoolThread::run() {
     LOGV("run");
     for (;;) {
-        SoundPoolMsg msg = mMessages.read();
+        SoundPoolMsg msg = read();
         LOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
         switch (msg.mMessageType) {
         case SoundPoolMsg::KILL:
@@ -95,14 +99,16 @@
 }
 
 void SoundPoolThread::loadSample(int sampleID) {
-    mMessages.write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
+    write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
 }
 
 void SoundPoolThread::doLoadSample(int sampleID) {
     sp <Sample> sample = mSoundPool->findSample(sampleID);
+    status_t status = -1;
     if (sample != 0) {
-        sample->doLoad();
+        status = sample->doLoad();
     }
+    mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
 }
 
 } // end namespace android
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
index 459a764..bbd35e0 100644
--- a/media/jni/soundpool/SoundPoolThread.h
+++ b/media/jni/soundpool/SoundPoolThread.h
@@ -27,7 +27,7 @@
 
 class SoundPoolMsg {
 public:
-    enum MessageType { INVALID, KILL, LOAD_SAMPLE, PLAY_SAMPLE, SAMPLE_DONE };
+    enum MessageType { INVALID, KILL, LOAD_SAMPLE };
     SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
     SoundPoolMsg(MessageType MessageType, int data) :
         mMessageType(MessageType), mData(data) {}
@@ -45,29 +45,22 @@
     SoundPoolThread(SoundPool* SoundPool);
     ~SoundPoolThread();
     void loadSample(int sampleID);
-    void quit() { mMessages.quit(); }
+    void quit();
+    void write(SoundPoolMsg msg);
 
 private:
     static const size_t maxMessages = 5;
 
-    class MessageQueue {
-    public:
-        void write(SoundPoolMsg msg);
-        const SoundPoolMsg read();
-        void setCapacity(size_t size) { mQueue.setCapacity(size); }
-        void quit();
-    private:
-        Vector<SoundPoolMsg>    mQueue;
-        Mutex                   mLock;
-        Condition               mCondition;
-    };
-
     static int beginThread(void* arg);
     int run();
     void doLoadSample(int sampleID);
+    const SoundPoolMsg read();
 
-    SoundPool*                  mSoundPool;
-    MessageQueue                mMessages;
+    Mutex                   mLock;
+    Condition               mCondition;
+    Vector<SoundPoolMsg>    mMsgQueue;
+    SoundPool*              mSoundPool;
+    bool                    mRunning;
 };
 
 } // end namespace android
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 1381db3..f6ea916 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -17,7 +17,7 @@
 #include <stdio.h>
 
 //#define LOG_NDEBUG 0
-#define LOG_TAG "SoundPool"
+#define LOG_TAG "SoundPool-JNI"
 
 #include <utils/Log.h>
 #include <nativehelper/jni.h>
@@ -29,6 +29,7 @@
 
 static struct fields_t {
     jfieldID    mNativeContext;
+    jmethodID   mPostEvent;
     jclass      mSoundPoolClass;
 } fields;
 
@@ -149,19 +150,29 @@
     ap->setRate(channelID, rate);
 }
 
-static void
-android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz,
-        jobject weak_this, jint maxChannels, jint streamType, jint srcQuality)
+static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, void* user)
+{
+    LOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->CallStaticVoidMethod(fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2, NULL);
+}
+
+static jint
+android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, jint maxChannels, jint streamType, jint srcQuality)
 {
     LOGV("android_media_SoundPool_native_setup");
     SoundPool *ap = new SoundPool(maxChannels, streamType, srcQuality);
     if (ap == NULL) {
-        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
-        return;
+        return -1;
     }
 
     // save pointer to SoundPool C++ object in opaque field in Java object
     env->SetIntField(thiz, fields.mNativeContext, (int)ap);
+
+    // set callback with weak reference
+    jobject globalWeakRef = env->NewGlobalRef(weakRef);
+    ap->setCallback(android_media_callback, globalWeakRef);
+    return 0;
 }
 
 static void
@@ -170,6 +181,15 @@
     LOGV("android_media_SoundPool_release");
     SoundPool *ap = MusterSoundPool(env, thiz);
     if (ap != NULL) {
+
+        // release weak reference
+        jobject weakRef = (jobject) ap->getUserData();
+        if (weakRef != NULL) {
+            env->DeleteGlobalRef(weakRef);
+        }
+
+        // clear callback and native context
+        ap->setCallback(NULL, NULL);
         env->SetIntField(thiz, fields.mNativeContext, 0);
         delete ap;
     }
@@ -224,7 +244,7 @@
         (void *)android_media_SoundPool_setRate
     },
     {   "native_setup",
-        "(Ljava/lang/Object;III)V",
+        "(Ljava/lang/Object;III)I",
         (void*)android_media_SoundPool_native_setup
     },
     {   "release",
@@ -259,6 +279,13 @@
         goto bail;
     }
 
+    fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
+                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+    if (fields.mPostEvent == NULL) {
+        LOGE("Can't find android/media/SoundPool.postEventFromNative");
+        return -1;
+    }
+
     if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
         goto bail;