Squashed commit of the following:

commit 144b1c40e9cf08a584c50e1bef7ba3f287e81a4f
Author: Andreas Huber <andih@google.com>
Date:   Wed Dec 16 09:28:23 2009 -0800

    This H264 file shows a certain problem even better.

commit 3245f1f3b7471975aeeb824a756c987abd610f55
Author: Andreas Huber <andih@google.com>
Date:   Wed Dec 16 09:20:08 2009 -0800

    Using only the QA testfiles now.

commit 074817eb3816c5dd70858a3594e3b92d799d873b
Author: Andreas Huber <andih@google.com>
Date:   Tue Dec 15 16:17:39 2009 -0800

    Yay, roles are back again now that the API is in place.

commit 6d847e4932cc38301ae27cb7283b7f1553a95457
Author: Andreas Huber <andih@google.com>
Date:   Tue Dec 15 13:01:20 2009 -0800

    Added commandline option for specifying the random seed for reproducable tests.

commit 62ab37b26336eaa67e49791c41c996acb6acee3f
Author: Andreas Huber <andih@google.com>
Date:   Mon Dec 14 10:53:27 2009 -0800

    When issuing a seek it is important that only the first MediaSource::read call has the seek option.

commit e77c46644b2fb6862bafa3569f7d304252074f1e
Author: Andreas Huber <andih@google.com>
Date:   Mon Dec 7 16:39:07 2009 -0800

    Make sure the tests are actually built, sp<OMXCodec> becomes sp<MediaSource>

commit 6df56915bd55a9445b3c6f953d3cc251d81579b8
Author: Andreas Huber <andih@google.com>
Date:   Thu Dec 3 14:25:36 2009 -0800

    Temporarily disable support for querying the roles of OMX components.

commit 31bb26930df9e3658dea684cedb4b0f1a06a4a88
Author: Andreas Huber <andih@google.com>
Date:   Tue Dec 1 13:36:52 2009 -0800

    Disregard EOS events, slightly change the way the EOS flag on output buffers is handled.

commit 4c382fbc9aebee8197d5988d04378062809e7c48
Author: Andreas Huber <andih@google.com>
Date:   Tue Dec 1 09:37:24 2009 -0800

    New random seek test for the codec tests. Fixed "sticky" end-of-output-buffers flag behaviour in OMXCodec.

commit c762eac3e44309592b61a168d66e091cf609fa03
Author: Andreas Huber <andih@google.com>
Date:   Tue Nov 3 14:13:43 2009 -0800

    Fix a typo.

commit 50540a59b65c7d476b0193c7494cd75895e6ca6d
Author: Andreas Huber <andih@google.com>
Date:   Tue Nov 3 09:48:35 2009 -0800

    Some more fine tuning of the unit tests, make MPEG4Extractor less verbose.

commit 1157a7e52a0636706caa235abe16d2ff8a0b8140
Author: Andreas Huber <andih@google.com>
Date:   Wed Oct 28 12:01:01 2009 -0700

    Changes to the IOMX::listNodes API, this now returns the component's roles as well, unit tests now test all components in all supported roles by default.

commit 30fbf2d8c6cb927689f7ba75eb550a81e9df488a
Author: Andreas Huber <andih@google.com>
Date:   Mon Oct 26 09:45:26 2009 -0700

    Initial check-in of unit tests for OMX components.
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 376f3d9..ad6540a 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -345,12 +345,12 @@
         sp<IOMX> omx = service->getOMX();
         CHECK(omx.get() != NULL);
 
-        List<String8> list;
+        List<IOMX::ComponentInfo> list;
         omx->listNodes(&list);
 
-        for (List<String8>::iterator it = list.begin();
+        for (List<IOMX::ComponentInfo>::iterator it = list.begin();
              it != list.end(); ++it) {
-            printf("%s\n", (*it).string());
+            printf("%s\n", (*it).mName.string());
         }
     }
 
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 6f3ba1c..39bd5b1 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -42,7 +42,11 @@
     typedef void *buffer_id;
     typedef void *node_id;
 
-    virtual status_t listNodes(List<String8> *list) = 0;
+    struct ComponentInfo {
+        String8 mName;
+        List<String8> mRoles;
+    };
+    virtual status_t listNodes(List<ComponentInfo> *list) = 0;
 
     virtual status_t allocateNode(
             const char *name, const sp<IOMXObserver> &observer,
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index 76a9e7d..b43e48f 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -75,7 +75,7 @@
         : BpInterface<IOMX>(impl) {
     }
 
-    virtual status_t listNodes(List<String8> *list) {
+    virtual status_t listNodes(List<ComponentInfo> *list) {
         list->clear();
 
         Parcel data, reply;
@@ -84,9 +84,14 @@
 
         int32_t n = reply.readInt32();
         for (int32_t i = 0; i < n; ++i) {
-            String8 s = reply.readString8();
+            list->push_back(ComponentInfo());
+            ComponentInfo &info = *--list->end();
 
-            list->push_back(s);
+            info.mName = reply.readString8();
+            int32_t numRoles = reply.readInt32();
+            for (int32_t j = 0; j < numRoles; ++j) {
+                info.mRoles.push_back(reply.readString8());
+            }
         }
 
         return OK;
@@ -368,13 +373,20 @@
         {
             CHECK_INTERFACE(IOMX, data, reply);
 
-            List<String8> list;
+            List<ComponentInfo> list;
             listNodes(&list);
 
             reply->writeInt32(list.size());
-            for (List<String8>::iterator it = list.begin();
+            for (List<ComponentInfo>::iterator it = list.begin();
                  it != list.end(); ++it) {
-                reply->writeString8(*it);
+                ComponentInfo &cur = *it;
+
+                reply->writeString8(cur.mName);
+                reply->writeInt32(cur.mRoles.size());
+                for (List<String8>::iterator role_it = cur.mRoles.begin();
+                     role_it != cur.mRoles.end(); ++role_it) {
+                    reply->writeString8(*role_it);
+                }
             }
 
             return NO_ERROR;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 7969710..143e8ee 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -534,8 +534,8 @@
             uint16_t sample_size = U16_AT(&buffer[18]);
             uint32_t sample_rate = U32_AT(&buffer[24]) >> 16;
 
-            printf("*** coding='%s' %d channels, size %d, rate %d\n",
-                   chunk, num_channels, sample_size, sample_rate);
+            // printf("*** coding='%s' %d channels, size %d, rate %d\n",
+            //        chunk, num_channels, sample_size, sample_rate);
 
             mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
             mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
@@ -576,8 +576,8 @@
             uint16_t width = U16_AT(&buffer[6 + 18]);
             uint16_t height = U16_AT(&buffer[6 + 20]);
 
-            printf("*** coding='%s' width=%d height=%d\n",
-                   chunk, width, height);
+            // printf("*** coding='%s' width=%d height=%d\n",
+            //        chunk, width, height);
 
             mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
             mLastTrack->meta->setInt32(kKeyWidth, width);
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 99c39f8..47bbda3 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -425,14 +425,9 @@
         esds.getCodecSpecificInfo(
                 &codec_specific_data, &codec_specific_data_size);
 
-        printf("found codec-specific data of size %d\n",
-               codec_specific_data_size);
-
         codec->addCodecSpecificData(
                 codec_specific_data, codec_specific_data_size);
     } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
-        printf("found avcc of size %d\n", size);
-
         // Parse the AVCDecoderConfigurationRecord
 
         const uint8_t *ptr = (const uint8_t *)data;
@@ -1223,7 +1218,7 @@
              portIndex == kPortIndexInput ? "input" : "output");
     }
 
-    dumpPortStatus(portIndex);
+    // dumpPortStatus(portIndex);
 
     return OK;
 }
@@ -1273,7 +1268,6 @@
                 CHECK_EQ(mPortStatus[kPortIndexInput], ENABLED);
                 drainInputBuffer(&buffers->editItemAt(i));
             }
-
             break;
         }
 
@@ -1282,12 +1276,10 @@
             IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer;
             OMX_U32 flags = msg.u.extended_buffer_data.flags;
 
-            CODEC_LOGV("FILL_BUFFER_DONE(buffer: %p, size: %ld, flags: 0x%08lx)",
+            CODEC_LOGV("FILL_BUFFER_DONE(buffer: %p, size: %ld, flags: 0x%08lx, timestamp: %lld us (%.2f secs))",
                  buffer,
                  msg.u.extended_buffer_data.range_length,
-                 flags);
-
-            CODEC_LOGV("FILL_BUFFER_DONE(timestamp: %lld us (%.2f secs))",
+                 flags,
                  msg.u.extended_buffer_data.timestamp,
                  msg.u.extended_buffer_data.timestamp / 1E6);
 
@@ -1315,11 +1307,13 @@
                 CHECK_EQ(err, OK);
 
                 buffers->removeAt(i);
+#if 0
             } else if (mPortStatus[kPortIndexOutput] == ENABLED
                        && (flags & OMX_BUFFERFLAG_EOS)) {
                 CODEC_LOGV("No more output data.");
                 mNoMoreOutputData = true;
                 mBufferFilled.signal();
+#endif
             } else if (mPortStatus[kPortIndexOutput] != SHUTTING_DOWN) {
                 CHECK_EQ(mPortStatus[kPortIndexOutput], ENABLED);
 
@@ -1351,6 +1345,11 @@
 
                 mFilledBuffers.push_back(i);
                 mBufferFilled.signal();
+
+                if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_EOS) {
+                    CODEC_LOGV("No more output data.");
+                    mNoMoreOutputData = true;
+                }
             }
 
             break;
@@ -1374,7 +1373,7 @@
 
         case OMX_EventError:
         {
-            LOGE("ERROR(%ld, %ld)", data1, data2);
+            LOGE("ERROR(0x%08lx, %ld)", data1, data2);
 
             setState(ERROR);
             break;
@@ -1386,6 +1385,7 @@
             break;
         }
 
+#if 0
         case OMX_EventBufferFlag:
         {
             CODEC_LOGV("EVENT_BUFFER_FLAG(%ld)", data1);
@@ -1395,6 +1395,7 @@
             }
             break;
         }
+#endif
 
         default:
         {
@@ -1565,13 +1566,6 @@
                     CODEC_LOGV("Finished flushing both ports, now continuing from"
                          " seek-time.");
 
-                    // Clear this flag in case the decoder sent us either
-                    // the EVENT_BUFFER_FLAG(1) or an output buffer with
-                    // the EOS flag set _while_ flushing. Since we're going
-                    // to submit "fresh" input data now, this flag no longer
-                    // applies to our future.
-                    mNoMoreOutputData = false;
-
                     drainInputBuffers();
                     fillOutputBuffers();
                 }
@@ -1832,6 +1826,8 @@
             memcpy(info->mMem->pointer(), specific->mData, specific->mSize);
         }
 
+        mNoMoreOutputData = false;
+
         status_t err = mOMX->emptyBuffer(
                 mNode, info->mBuffer, 0, size,
                 OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
@@ -1849,7 +1845,9 @@
     if (mSeekTimeUs >= 0) {
         MediaSource::ReadOptions options;
         options.setSeekTo(mSeekTimeUs);
+
         mSeekTimeUs = -1;
+        mBufferFilled.signal();
 
         err = mSource->read(&srcBuffer, &options);
     } else {
@@ -1866,6 +1864,8 @@
 
         mSignalledEOS = true;
     } else {
+        mNoMoreOutputData = false;
+
         srcLength = srcBuffer->range_length();
 
         if (info->mMem->size() < srcLength) {
@@ -1878,10 +1878,10 @@
                srcLength);
 
         if (srcBuffer->meta_data()->findInt64(kKeyTime, &timestampUs)) {
-            CODEC_LOGV("Calling emptyBuffer on buffer %p (length %d)",
-                 info->mBuffer, srcLength);
-            CODEC_LOGV("Calling emptyBuffer with timestamp %lld us (%.2f secs)",
-                 timestampUs, timestampUs / 1E6);
+            CODEC_LOGV("Calling emptyBuffer on buffer %p (length %d), "
+                       "timestamp %lld us (%.2f secs)",
+                       info->mBuffer, srcLength,
+                       timestampUs, timestampUs / 1E6);
         }
     }
 
@@ -2298,7 +2298,6 @@
         CODEC_LOGV("seeking to %lld us (%.2f secs)", seekTimeUs, seekTimeUs / 1E6);
 
         mSignalledEOS = false;
-        mNoMoreOutputData = false;
 
         CHECK(seekTimeUs >= 0);
         mSeekTimeUs = seekTimeUs;
@@ -2317,6 +2316,10 @@
         if (emulateOutputFlushCompletion) {
             onCmdComplete(OMX_CommandFlush, kPortIndexOutput);
         }
+
+        while (mSeekTimeUs >= 0) {
+            mBufferFilled.wait(mLock);
+        }
     }
 
     while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index 01b8e7a..ce0b0d55aa 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -31,7 +31,7 @@
 public:
     OMX();
 
-    virtual status_t listNodes(List<String8> *list);
+    virtual status_t listNodes(List<ComponentInfo> *list);
 
     virtual status_t allocateNode(
             const char *name, const sp<IOMXObserver> &observer, node_id *node);
diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h
index 19d3940..923b801 100644
--- a/media/libstagefright/include/OMXNodeInstance.h
+++ b/media/libstagefright/include/OMXNodeInstance.h
@@ -85,6 +85,7 @@
     OMX::node_id mNodeID;
     OMX_HANDLETYPE mHandle;
     sp<IOMXObserver> mObserver;
+    bool mDying;
 
     struct ActiveBuffer {
         OMX_U32 mPortIndex;
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
index 4f88d99..2473731 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -47,3 +47,6 @@
 LOCAL_MODULE:= libstagefright_omx
 
 include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 94dbb6d..8c3f252 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -208,39 +208,27 @@
     instance->onObserverDied(mMaster);
 }
 
-#if 0
-static void dumpRoles(OMXMaster *master, const char *name) {
-    Vector<String8> roles;
-    OMX_ERRORTYPE err = master->getRolesOfComponent(name, &roles);
-
-    if (err != OMX_ErrorNone) {
-        LOGE("Could not get roles for component '%s'.", name);
-        return;
-    }
-
-    if (roles.isEmpty()) {
-        LOGE("Component '%s' has NO roles!", name);
-        return;
-    }
-
-    LOGI("Component '%s' has the following roles:", name);
-
-    for (size_t i = 0; i < roles.size(); ++i) {
-        LOGI("%d) %s", i + 1, roles[i].string());
-    }
-}
-#endif
-
-status_t OMX::listNodes(List<String8> *list) {
+status_t OMX::listNodes(List<ComponentInfo> *list) {
     list->clear();
 
     OMX_U32 index = 0;
     char componentName[256];
     while (mMaster->enumerateComponents(
                 componentName, sizeof(componentName), index) == OMX_ErrorNone) {
-        list->push_back(String8(componentName));
+        list->push_back(ComponentInfo());
+        ComponentInfo &info = *--list->end();
 
-        // dumpRoles(mMaster, componentName);
+        info.mName = componentName;
+
+        Vector<String8> roles;
+        OMX_ERRORTYPE err =
+            mMaster->getRolesOfComponent(componentName, &roles);
+
+        if (err == OMX_ErrorNone) {
+            for (OMX_U32 i = 0; i < roles.size(); ++i) {
+                info.mRoles.push_back(roles[i]);
+            }
+        }
 
         ++index;
     }
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index 288710e..4eb6417 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -78,7 +78,8 @@
     : mOwner(owner),
       mNodeID(NULL),
       mHandle(NULL),
-      mObserver(observer) {
+      mObserver(observer),
+      mDying(false) {
 }
 
 OMXNodeInstance::~OMXNodeInstance() {
@@ -114,6 +115,11 @@
     // for components that don't do this themselves on a call to
     // "FreeHandle".
 
+    // The code below may trigger some more events to be dispatched
+    // by the OMX component - we want to ignore them as our client
+    // does not expect them.
+    mDying = true;
+
     OMX_STATETYPE state;
     CHECK_EQ(OMX_GetState(mHandle, &state), OMX_ErrorNone);
     switch (state) {
@@ -406,6 +412,9 @@
         OMX_IN OMX_U32 nData2,
         OMX_IN OMX_PTR pEventData) {
     OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
+    if (instance->mDying) {
+        return OMX_ErrorNone;
+    }
     return instance->owner()->OnEvent(
             instance->nodeID(), eEvent, nData1, nData2, pEventData);
 }
@@ -416,6 +425,9 @@
         OMX_IN OMX_PTR pAppData,
         OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
     OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
+    if (instance->mDying) {
+        return OMX_ErrorNone;
+    }
     return instance->owner()->OnEmptyBufferDone(instance->nodeID(), pBuffer);
 }
 
@@ -425,6 +437,9 @@
         OMX_IN OMX_PTR pAppData,
         OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
     OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
+    if (instance->mDying) {
+        return OMX_ErrorNone;
+    }
     return instance->owner()->OnFillBufferDone(instance->nodeID(), pBuffer);
 }
 
diff --git a/media/libstagefright/omx/tests/Android.mk b/media/libstagefright/omx/tests/Android.mk
new file mode 100644
index 0000000..cd654510
--- /dev/null
+++ b/media/libstagefright/omx/tests/Android.mk
@@ -0,0 +1,23 @@
+ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES =       \
+	OMXHarness.cpp  \
+
+LOCAL_SHARED_LIBRARIES := \
+	libstagefright
+
+LOCAL_C_INCLUDES:= \
+	$(JNI_H_INCLUDE) \
+	frameworks/base/media/libstagefright \
+	$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
+
+LOCAL_MODULE:= omx_tests
+
+include $(BUILD_EXECUTABLE)
+
+endif
+
+
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
new file mode 100644
index 0000000..2e23899
--- /dev/null
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2009 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_NDEBUG 0
+#define LOG_TAG "OMXHarness"
+#include <utils/Log.h>
+
+#include "OMXHarness.h"
+
+#include <sys/time.h>
+
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <binder/MemoryDealer.h>
+#include <media/IMediaPlayerService.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXCodec.h>
+
+#define DEFAULT_TIMEOUT         500000
+
+namespace android {
+
+static int64_t getNowUs() {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+
+    return (int64_t)tv.tv_usec + tv.tv_sec * 1000000;
+}
+
+Harness::Harness()
+    : mInitCheck(NO_INIT) {
+    mInitCheck = initOMX();
+}
+
+Harness::~Harness() {
+}
+
+status_t Harness::initCheck() const {
+    return mInitCheck;
+}
+
+status_t Harness::initOMX() {
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binder = sm->getService(String16("media.player"));
+    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
+    mOMX = service->getOMX();
+
+    return mOMX != 0 ? OK : NO_INIT;
+}
+
+void Harness::onMessage(const omx_message &msg) {
+    Mutex::Autolock autoLock(mLock);
+    mMessageQueue.push_back(msg);
+    mMessageAddedCondition.signal();
+}
+
+status_t Harness::dequeueMessageForNode(
+        IOMX::node_id node, omx_message *msg, int64_t timeoutUs) {
+    return dequeueMessageForNodeIgnoringBuffers(
+            node, NULL, NULL, msg, timeoutUs);
+}
+
+// static
+bool Harness::handleBufferMessage(
+        const omx_message &msg,
+        Vector<Buffer> *inputBuffers,
+        Vector<Buffer> *outputBuffers) {
+    switch (msg.type) {
+        case omx_message::EMPTY_BUFFER_DONE:
+        {
+            if (inputBuffers) {
+                for (size_t i = 0; i < inputBuffers->size(); ++i) {
+                    if ((*inputBuffers)[i].mID == msg.u.buffer_data.buffer) {
+                        inputBuffers->editItemAt(i).mFlags &= ~kBufferBusy;
+                        return true;
+                    }
+                }
+                CHECK(!"should not be here");
+            }
+            break;
+        }
+
+        case omx_message::FILL_BUFFER_DONE:
+        {
+            if (outputBuffers) {
+                for (size_t i = 0; i < outputBuffers->size(); ++i) {
+                    if ((*outputBuffers)[i].mID == msg.u.buffer_data.buffer) {
+                        outputBuffers->editItemAt(i).mFlags &= ~kBufferBusy;
+                        return true;
+                    }
+                }
+                CHECK(!"should not be here");
+            }
+            break;
+        }
+
+        default:
+            break;
+    }
+
+    return false;
+}
+
+status_t Harness::dequeueMessageForNodeIgnoringBuffers(
+        IOMX::node_id node,
+        Vector<Buffer> *inputBuffers,
+        Vector<Buffer> *outputBuffers,
+        omx_message *msg, int64_t timeoutUs) {
+    int64_t finishBy = getNowUs() + timeoutUs;
+
+    for (;;) {
+        Mutex::Autolock autoLock(mLock);
+        List<omx_message>::iterator it = mMessageQueue.begin();
+        while (it != mMessageQueue.end()) {
+            if ((*it).node == node) {
+                if (handleBufferMessage(*it, inputBuffers, outputBuffers)) {
+                    it = mMessageQueue.erase(it);
+                    continue;
+                }
+
+                *msg = *it;
+                mMessageQueue.erase(it);
+
+                return OK;
+            }
+
+            ++it;
+        }
+
+        status_t err = (timeoutUs < 0)
+            ? mMessageAddedCondition.wait(mLock)
+            : mMessageAddedCondition.waitRelative(
+                    mLock, (finishBy - getNowUs()) * 1000);
+
+        if (err == TIMED_OUT) {
+            return err;
+        }
+        CHECK_EQ(err, OK);
+    }
+}
+
+status_t Harness::getPortDefinition(
+        IOMX::node_id node, OMX_U32 portIndex,
+        OMX_PARAM_PORTDEFINITIONTYPE *def) {
+    def->nSize = sizeof(*def);
+    def->nVersion.s.nVersionMajor = 1;
+    def->nVersion.s.nVersionMinor = 0;
+    def->nVersion.s.nRevision = 0;
+    def->nVersion.s.nStep = 0;
+    def->nPortIndex = portIndex;
+    return mOMX->getParameter(
+            node, OMX_IndexParamPortDefinition, def, sizeof(*def));
+}
+
+#define EXPECT(condition, info) \
+    if (!(condition)) {         \
+        LOGE(info); printf("\n  * " info "\n"); return UNKNOWN_ERROR; \
+    }
+
+#define EXPECT_SUCCESS(err, info) \
+    EXPECT((err) == OK, info " failed")
+
+status_t Harness::allocatePortBuffers(
+        const sp<MemoryDealer> &dealer,
+        IOMX::node_id node, OMX_U32 portIndex,
+        Vector<Buffer> *buffers) {
+    buffers->clear();
+
+    OMX_PARAM_PORTDEFINITIONTYPE def;
+    status_t err = getPortDefinition(node, portIndex, &def);
+    EXPECT_SUCCESS(err, "getPortDefinition");
+
+    for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
+        Buffer buffer;
+        buffer.mMemory = dealer->allocate(def.nBufferSize);
+        buffer.mFlags = 0;
+        CHECK(buffer.mMemory != NULL);
+
+        err = mOMX->allocateBufferWithBackup(
+                node, portIndex, buffer.mMemory, &buffer.mID);
+        EXPECT_SUCCESS(err, "allocateBuffer");
+
+        buffers->push(buffer);
+    }
+
+    return OK;
+}
+
+status_t Harness::setRole(IOMX::node_id node, const char *role) {
+    OMX_PARAM_COMPONENTROLETYPE params;
+    params.nSize = sizeof(params);
+    params.nVersion.s.nVersionMajor = 1;
+    params.nVersion.s.nVersionMinor = 0;
+    params.nVersion.s.nRevision = 0;
+    params.nVersion.s.nStep = 0;
+    strncpy((char *)params.cRole, role, OMX_MAX_STRINGNAME_SIZE - 1);
+    params.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0';
+
+    return mOMX->setParameter(
+            node, OMX_IndexParamStandardComponentRole,
+            &params, sizeof(params));
+}
+
+struct NodeReaper {
+    NodeReaper(const sp<Harness> &harness, IOMX::node_id node)
+        : mHarness(harness),
+          mNode(node) {
+    }
+
+    ~NodeReaper() {
+        if (mNode != 0) {
+            mHarness->mOMX->freeNode(mNode);
+            mNode = 0;
+        }
+    }
+
+    void disarm() {
+        mNode = 0;
+    }
+
+private:
+    sp<Harness> mHarness;
+    IOMX::node_id mNode;
+
+    NodeReaper(const NodeReaper &);
+    NodeReaper &operator=(const NodeReaper &);
+};
+
+static sp<MediaSource> MakeSource(
+        const char *uri,
+        const char *mimeType) {
+    sp<MediaExtractor> extractor = MediaExtractor::CreateFromURI(uri);
+
+    if (extractor == NULL) {
+        return NULL;
+    }
+
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+        const char *trackMIME;
+        CHECK(meta->findCString(kKeyMIMEType, &trackMIME));
+
+        if (!strcasecmp(trackMIME, mimeType)) {
+            return extractor->getTrack(i);
+        }
+    }
+
+    return NULL;
+}
+
+status_t Harness::testStateTransitions(
+        const char *componentName, const char *componentRole) {
+    if (strncmp(componentName, "OMX.", 4)) {
+        // Non-OMX components, i.e. software decoders won't execute this
+        // test.
+        return OK;
+    }
+
+    sp<MemoryDealer> dealer = new MemoryDealer(8 * 1024 * 1024);
+    IOMX::node_id node;
+
+    status_t err =
+        mOMX->allocateNode(componentName, this, &node);
+    EXPECT_SUCCESS(err, "allocateNode");
+
+    NodeReaper reaper(this, node);
+
+    err = setRole(node, componentRole);
+    EXPECT_SUCCESS(err, "setRole");
+
+    // Initiate transition Loaded->Idle
+    err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateIdle);
+    EXPECT_SUCCESS(err, "sendCommand(go-to-Idle)");
+
+    omx_message msg;
+    err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT);
+    // Make sure node doesn't just transition to idle before we are done
+    // allocating all input and output buffers.
+    EXPECT(err == TIMED_OUT,
+            "Component must not transition from loaded to idle before "
+            "all input and output buffers are allocated.");
+
+    // Now allocate buffers.
+    Vector<Buffer> inputBuffers;
+    err = allocatePortBuffers(dealer, node, 0, &inputBuffers);
+    EXPECT_SUCCESS(err, "allocatePortBuffers(input)");
+
+    err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT);
+    CHECK_EQ(err, TIMED_OUT);
+
+    Vector<Buffer> outputBuffers;
+    err = allocatePortBuffers(dealer, node, 1, &outputBuffers);
+    EXPECT_SUCCESS(err, "allocatePortBuffers(output)");
+
+    err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT);
+    EXPECT(err == OK
+            && msg.type == omx_message::EVENT
+            && msg.u.event_data.event == OMX_EventCmdComplete
+            && msg.u.event_data.data1 == OMX_CommandStateSet
+            && msg.u.event_data.data2 == OMX_StateIdle,
+           "Component did not properly transition to idle state "
+           "after all input and output buffers were allocated.");
+
+    // Initiate transition Idle->Executing
+    err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateExecuting);
+    EXPECT_SUCCESS(err, "sendCommand(go-to-Executing)");
+
+    err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT);
+    EXPECT(err == OK
+            && msg.type == omx_message::EVENT
+            && msg.u.event_data.event == OMX_EventCmdComplete
+            && msg.u.event_data.data1 == OMX_CommandStateSet
+            && msg.u.event_data.data2 == OMX_StateExecuting,
+           "Component did not properly transition from idle to "
+           "executing state.");
+
+    for (size_t i = 0; i < outputBuffers.size(); ++i) {
+        err = mOMX->fillBuffer(node, outputBuffers[i].mID);
+        EXPECT_SUCCESS(err, "fillBuffer");
+
+        outputBuffers.editItemAt(i).mFlags |= kBufferBusy;
+    }
+
+    err = mOMX->sendCommand(node, OMX_CommandFlush, 1);
+    EXPECT_SUCCESS(err, "sendCommand(flush-output-port)");
+
+    err = dequeueMessageForNodeIgnoringBuffers(
+            node, &inputBuffers, &outputBuffers, &msg, DEFAULT_TIMEOUT);
+    EXPECT(err == OK
+            && msg.type == omx_message::EVENT
+            && msg.u.event_data.event == OMX_EventCmdComplete
+            && msg.u.event_data.data1 == OMX_CommandFlush
+            && msg.u.event_data.data2 == 1,
+           "Component did not properly acknowledge flushing the output port.");
+
+    for (size_t i = 0; i < outputBuffers.size(); ++i) {
+        EXPECT((outputBuffers[i].mFlags & kBufferBusy) == 0,
+               "Not all output buffers have been returned to us by the time "
+               "we received the flush-complete notification.");
+    }
+
+    for (size_t i = 0; i < outputBuffers.size(); ++i) {
+        err = mOMX->fillBuffer(node, outputBuffers[i].mID);
+        EXPECT_SUCCESS(err, "fillBuffer");
+
+        outputBuffers.editItemAt(i).mFlags |= kBufferBusy;
+    }
+
+    // Initiate transition Executing->Idle
+    err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateIdle);
+    EXPECT_SUCCESS(err, "sendCommand(go-to-Idle)");
+
+    err = dequeueMessageForNodeIgnoringBuffers(
+            node, &inputBuffers, &outputBuffers, &msg, DEFAULT_TIMEOUT);
+    EXPECT(err == OK
+            && msg.type == omx_message::EVENT
+            && msg.u.event_data.event == OMX_EventCmdComplete
+            && msg.u.event_data.data1 == OMX_CommandStateSet
+            && msg.u.event_data.data2 == OMX_StateIdle,
+           "Component did not properly transition to from executing to "
+           "idle state.");
+
+    for (size_t i = 0; i < inputBuffers.size(); ++i) {
+        EXPECT((inputBuffers[i].mFlags & kBufferBusy) == 0,
+                "Not all input buffers have been returned to us by the "
+                "time we received the transition-to-idle complete "
+                "notification.");
+    }
+
+    for (size_t i = 0; i < outputBuffers.size(); ++i) {
+        EXPECT((outputBuffers[i].mFlags & kBufferBusy) == 0,
+                "Not all output buffers have been returned to us by the "
+                "time we received the transition-to-idle complete "
+                "notification.");
+    }
+
+    // Initiate transition Idle->Loaded
+    err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateLoaded);
+    EXPECT_SUCCESS(err, "sendCommand(go-to-Loaded)");
+
+    // Make sure node doesn't just transition to loaded before we are done
+    // freeing all input and output buffers.
+    err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT);
+    CHECK_EQ(err, TIMED_OUT);
+
+    for (size_t i = 0; i < inputBuffers.size(); ++i) {
+        err = mOMX->freeBuffer(node, 0, inputBuffers[i].mID);
+        EXPECT_SUCCESS(err, "freeBuffer");
+    }
+
+    err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT);
+    CHECK_EQ(err, TIMED_OUT);
+
+    for (size_t i = 0; i < outputBuffers.size(); ++i) {
+        err = mOMX->freeBuffer(node, 1, outputBuffers[i].mID);
+        EXPECT_SUCCESS(err, "freeBuffer");
+    }
+
+    err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT);
+    EXPECT(err == OK
+            && msg.type == omx_message::EVENT
+            && msg.u.event_data.event == OMX_EventCmdComplete
+            && msg.u.event_data.data1 == OMX_CommandStateSet
+            && msg.u.event_data.data2 == OMX_StateLoaded,
+           "Component did not properly transition to from idle to "
+           "loaded state after freeing all input and output buffers.");
+
+    err = mOMX->freeNode(node);
+    EXPECT_SUCCESS(err, "freeNode");
+
+    reaper.disarm();
+
+    node = 0;
+
+    return OK;
+}
+
+static const char *GetMimeFromComponentRole(const char *componentRole) {
+    struct RoleToMime {
+        const char *mRole;
+        const char *mMime;
+    };
+    const RoleToMime kRoleToMime[] = {
+        { "video_decoder.avc", "video/avc" },
+        { "video_decoder.mpeg4", "video/mp4v-es" },
+        { "video_decoder.h263", "video/3gpp" },
+
+        // we appear to use this as a synonym to amrnb.
+        { "audio_decoder.amr", "audio/3gpp" },
+
+        { "audio_decoder.amrnb", "audio/3gpp" },
+        { "audio_decoder.amrwb", "audio/amr-wb" },
+        { "audio_decoder.aac", "audio/mp4a-latm" },
+        { "audio_decoder.mp3", "audio/mpeg" }
+    };
+
+    for (size_t i = 0; i < sizeof(kRoleToMime) / sizeof(kRoleToMime[0]); ++i) {
+        if (!strcmp(componentRole, kRoleToMime[i].mRole)) {
+            return kRoleToMime[i].mMime;
+        }
+    }
+
+    return NULL;
+}
+
+static const char *GetURLForMime(const char *mime) {
+    struct MimeToURL {
+        const char *mMime;
+        const char *mURL;
+    };
+    static const MimeToURL kMimeToURL[] = {
+        { "video/avc",
+          "file:///sdcard/media_api/video/H264_AAC.3gp" },
+        { "video/mp4v-es", "file:///sdcard/media_api/video/gingerkids.MP4" },
+        { "video/3gpp",
+          "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" },
+        { "audio/3gpp",
+          "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" },
+        { "audio/amr-wb",
+          "file:///sdcard/media_api/music_perf/AMRWB/"
+          "NIN_AMR-WB_15.85kbps_16kbps.amr" },
+        { "audio/mp4a-latm",
+          "file:///sdcard/media_api/music_perf/AAC/"
+          "WC_AAC_80kbps_32khz_Stereo_1pCBR_SSE.mp4" },
+        { "audio/mpeg",
+          "file:///sdcard/media_api/music_perf/MP3/"
+          "WC_256kbps_44.1khz_mono_CBR_DPA.mp3" }
+    };
+
+    for (size_t i = 0; i < sizeof(kMimeToURL) / sizeof(kMimeToURL[0]); ++i) {
+        if (!strcasecmp(kMimeToURL[i].mMime, mime)) {
+            return kMimeToURL[i].mURL;
+        }
+    }
+
+    return NULL;
+}
+
+static sp<MediaSource> CreateSourceForMime(const char *mime) {
+    const char *url = GetURLForMime(mime);
+    CHECK(url != NULL);
+
+    sp<MediaExtractor> extractor = MediaExtractor::CreateFromURI(url);
+
+    CHECK(extractor != NULL);
+
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<MetaData> meta = extractor->getTrackMetaData(i);
+        CHECK(meta != NULL);
+
+        const char *trackMime;
+        CHECK(meta->findCString(kKeyMIMEType, &trackMime));
+
+        if (!strcasecmp(mime, trackMime)) {
+            return extractor->getTrack(i);
+        }
+    }
+
+    return NULL;
+}
+
+static double uniform_rand() {
+    return (double)rand() / RAND_MAX;
+}
+
+static bool CloseEnough(int64_t time1Us, int64_t time2Us) {
+#if 0
+    int64_t diff = time1Us - time2Us;
+    if (diff < 0) {
+        diff = -diff;
+    }
+
+    return diff <= 50000;
+#else
+    return time1Us == time2Us;
+#endif
+}
+
+status_t Harness::testSeek(
+        const char *componentName, const char *componentRole) {
+    bool isEncoder =
+        !strncmp(componentRole, "audio_encoder.", 14)
+        || !strncmp(componentRole, "video_encoder.", 14);
+
+    if (isEncoder) {
+        // Not testing seek behaviour for encoders.
+
+        printf("  * Not testing seek functionality for encoders.\n");
+        return OK;
+    }
+
+    const char *mime = GetMimeFromComponentRole(componentRole);
+
+    if (!mime) {
+        LOGI("Cannot perform seek test with this componentRole (%s)",
+             componentRole);
+
+        return OK;
+    }
+
+    sp<MediaSource> source = CreateSourceForMime(mime);
+
+    sp<MediaSource> seekSource = CreateSourceForMime(mime);
+    CHECK_EQ(seekSource->start(), OK);
+
+    sp<MediaSource> codec = OMXCodec::Create(
+            mOMX, source->getFormat(), false /* createEncoder */,
+            source, componentName);
+
+    CHECK(codec != NULL);
+
+    CHECK_EQ(codec->start(), OK);
+
+    int64_t durationUs;
+    CHECK(source->getFormat()->findInt64(kKeyDuration, &durationUs));
+
+    LOGI("stream duration is %lld us (%.2f secs)",
+         durationUs, durationUs / 1E6);
+
+    static const int32_t kNumIterations = 5000;
+
+    for (int32_t i = 0; i < kNumIterations; ++i) {
+        int64_t requestedSeekTimeUs;
+        int64_t actualSeekTimeUs;
+        MediaSource::ReadOptions options;
+
+        double r = uniform_rand();
+
+        if (r < 0.5) {
+            // 50% chance of just continuing to decode from last position.
+
+            requestedSeekTimeUs = -1;
+
+            LOGI("requesting linear read");
+        } else {
+            if (r < 0.55) {
+                // 5% chance of seeking beyond end of stream.
+
+                requestedSeekTimeUs = durationUs;
+
+                LOGI("requesting seek beyond EOF");
+            } else {
+                requestedSeekTimeUs =
+                    (int64_t)(uniform_rand() * durationUs);
+
+                LOGI("requesting seek to %lld us (%.2f secs)",
+                     requestedSeekTimeUs, requestedSeekTimeUs / 1E6);
+            }
+
+            MediaBuffer *buffer;
+            options.setSeekTo(requestedSeekTimeUs);
+            if (seekSource->read(&buffer, &options) != OK) {
+                CHECK_EQ(buffer, NULL);
+                actualSeekTimeUs = -1;
+            } else {
+                CHECK(buffer != NULL);
+                CHECK(buffer->meta_data()->findInt64(kKeyTime, &actualSeekTimeUs));
+                CHECK(actualSeekTimeUs >= 0);
+
+                buffer->release();
+                buffer = NULL;
+            }
+
+            LOGI("nearest keyframe is at %lld us (%.2f secs)",
+                 actualSeekTimeUs, actualSeekTimeUs / 1E6);
+        }
+
+        status_t err;
+        MediaBuffer *buffer;
+        for (;;) {
+            err = codec->read(&buffer, &options);
+            options.clearSeekTo();
+            if (err == INFO_FORMAT_CHANGED) {
+                CHECK_EQ(buffer, NULL);
+                continue;
+            }
+            if (err == OK) {
+                CHECK(buffer != NULL);
+                if (buffer->range_length() == 0) {
+                    buffer->release();
+                    buffer = NULL;
+                    continue;
+                }
+            } else {
+                CHECK_EQ(buffer, NULL);
+            }
+
+            break;
+        }
+
+        if (requestedSeekTimeUs < 0) {
+            // Linear read.
+            if (err != OK) {
+                CHECK_EQ(buffer, NULL);
+            } else {
+                CHECK(buffer != NULL);
+                buffer->release();
+                buffer = NULL;
+            }
+        } else if (actualSeekTimeUs < 0) {
+            CHECK(err != OK);
+            CHECK_EQ(buffer, NULL);
+        } else {
+            EXPECT(err == OK,
+                   "Expected a valid buffer to be returned from "
+                   "OMXCodec::read.");
+            CHECK(buffer != NULL);
+
+            int64_t bufferTimeUs;
+            CHECK(buffer->meta_data()->findInt64(kKeyTime, &bufferTimeUs));
+            if (!CloseEnough(bufferTimeUs, actualSeekTimeUs)) {
+                printf("\n  * Attempted seeking to %lld us (%.2f secs)",
+                       requestedSeekTimeUs, requestedSeekTimeUs / 1E6);
+                printf("\n  * Nearest keyframe is at %lld us (%.2f secs)",
+                       actualSeekTimeUs, actualSeekTimeUs / 1E6);
+                printf("\n  * Returned buffer was at %lld us (%.2f secs)\n\n",
+                       bufferTimeUs, bufferTimeUs / 1E6);
+
+                buffer->release();
+                buffer = NULL;
+
+                CHECK_EQ(codec->stop(), OK);
+
+                return UNKNOWN_ERROR;
+            }
+
+            buffer->release();
+            buffer = NULL;
+        }
+    }
+
+    CHECK_EQ(codec->stop(), OK);
+
+    return OK;
+}
+
+status_t Harness::test(
+        const char *componentName, const char *componentRole) {
+    printf("testing %s [%s] ... ", componentName, componentRole);
+    LOGI("testing %s [%s].", componentName, componentRole);
+
+    status_t err1 = testStateTransitions(componentName, componentRole);
+    status_t err2 = testSeek(componentName, componentRole);
+
+    if (err1 != OK) {
+        return err1;
+    }
+
+    return err2;
+}
+
+status_t Harness::testAll() {
+    List<IOMX::ComponentInfo> componentInfos;
+    status_t err = mOMX->listNodes(&componentInfos);
+    EXPECT_SUCCESS(err, "listNodes");
+
+    for (List<IOMX::ComponentInfo>::iterator it = componentInfos.begin();
+         it != componentInfos.end(); ++it) {
+        const IOMX::ComponentInfo &info = *it;
+        const char *componentName = info.mName.string();
+
+        for (List<String8>::const_iterator role_it = info.mRoles.begin();
+             role_it != info.mRoles.end(); ++role_it) {
+            const char *componentRole = (*role_it).string();
+
+            err = test(componentName, componentRole);
+
+            if (err == OK) {
+                printf("OK\n");
+            }
+        }
+    }
+
+    return OK;
+}
+
+}  // namespace android
+
+static void usage(const char *me) {
+    fprintf(stderr, "usage: %s\n"
+                    "  -h(elp)  Show this information\n"
+                    "  -s(eed)  Set the random seed\n"
+                    "    [ component role ]\n\n"
+                    "When launched without specifying a specific component "
+                    "and role, tool will test all available OMX components "
+                    "in all their supported roles. To determine available "
+                    "component names, use \"stagefright -l\"\n"
+                    "It's also a good idea to run a separate \"adb logcat\""
+                    " for additional debug and progress information.", me);
+
+    exit(0);
+}
+
+int main(int argc, char **argv) {
+    using namespace android;
+
+    android::ProcessState::self()->startThreadPool();
+    DataSource::RegisterDefaultSniffers();
+
+    const char *me = argv[0];
+
+    unsigned long seed = 0xdeadbeef;
+
+    int res;
+    while ((res = getopt(argc, argv, "hs:")) >= 0) {
+        switch (res) {
+            case 's':
+            {
+                char *end;
+                unsigned long x = strtoul(optarg, &end, 10);
+
+                if (*end != '\0' || end == optarg) {
+                    fprintf(stderr, "Malformed seed.\n");
+                    return 1;
+                }
+
+                seed = x;
+                break;
+            }
+
+            case '?':
+                fprintf(stderr, "\n");
+                // fall through
+
+            case 'h':
+            default:
+            {
+                usage(me);
+                exit(1);
+                break;
+            }
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    printf("To reproduce the conditions for this test, launch "
+           "with \"%s -s %lu\"\n", me, seed);
+
+    srand(seed);
+
+    sp<Harness> h = new Harness;
+    CHECK_EQ(h->initCheck(), OK);
+
+    if (argc == 0) {
+        h->testAll();
+    } else if (argc == 2) {
+        if (h->test(argv[0], argv[1]) == OK) {
+            printf("OK\n");
+        }
+    }
+
+    return 0;
+}
diff --git a/media/libstagefright/omx/tests/OMXHarness.h b/media/libstagefright/omx/tests/OMXHarness.h
new file mode 100644
index 0000000..bb8fd0c
--- /dev/null
+++ b/media/libstagefright/omx/tests/OMXHarness.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2009 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 OMX_HARNESS_H_
+
+#define OMX_HARNESS_H_
+
+#include <media/IOMX.h>
+#include <utils/List.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include <OMX_Component.h>
+
+namespace android {
+
+class MemoryDealer;
+
+struct Harness : public BnOMXObserver {
+    enum BufferFlags {
+        kBufferBusy = 1
+    };
+    struct Buffer {
+        IOMX::buffer_id mID;
+        sp<IMemory> mMemory;
+        uint32_t mFlags;
+    };
+
+    Harness();
+
+    status_t initCheck() const;
+
+    status_t dequeueMessageForNode(
+            IOMX::node_id node, omx_message *msg, int64_t timeoutUs = -1);
+
+    status_t dequeueMessageForNodeIgnoringBuffers(
+            IOMX::node_id node,
+            Vector<Buffer> *inputBuffers,
+            Vector<Buffer> *outputBuffers,
+            omx_message *msg, int64_t timeoutUs = -1);
+
+    status_t getPortDefinition(
+            IOMX::node_id node, OMX_U32 portIndex,
+            OMX_PARAM_PORTDEFINITIONTYPE *def);
+
+    status_t allocatePortBuffers(
+            const sp<MemoryDealer> &dealer,
+            IOMX::node_id node, OMX_U32 portIndex,
+            Vector<Buffer> *buffers);
+
+    status_t setRole(IOMX::node_id node, const char *role);
+
+    status_t testStateTransitions(
+            const char *componentName, const char *componentRole);
+
+    status_t testSeek(
+            const char *componentName, const char *componentRole);
+
+    status_t test(
+            const char *componentName, const char *componentRole);
+
+    status_t testAll();
+
+    virtual void onMessage(const omx_message &msg);
+
+protected:
+    virtual ~Harness();
+
+private:
+    friend struct NodeReaper;
+
+    Mutex mLock;
+
+    status_t mInitCheck;
+    sp<IOMX> mOMX;
+    List<omx_message> mMessageQueue;
+    Condition mMessageAddedCondition;
+
+    status_t initOMX();
+
+    bool handleBufferMessage(
+            const omx_message &msg,
+            Vector<Buffer> *inputBuffers,
+            Vector<Buffer> *outputBuffers);
+
+    Harness(const Harness &);
+    Harness &operator=(const Harness &);
+};
+
+}  // namespace android
+
+#endif  // OMX_HARNESS_H_