cts_audio work: initial code
- volume calibration and THD test case work tuned.
- 59 unit tests pass
- local audio playback / recording works : uses tinyalsa
- host to device ptorocol in host side implemented / tested (with loopback)
- device side recording / playback works for test cases / test_io.xml
- python processing baseline added.
- spectrum algorithm implementated: calculate Transfer Function of device
  over host and check if amplitudes are within margain. Needs parameter tuning
- spectrum test needs some improvements due to the non-flat response from
  ref microphone.

Change-Id: I5288dec42f272260f30f1ace1374d365e31d1664
diff --git a/suite/audio_quality/lib/src/task/ModelBuilder.cpp b/suite/audio_quality/lib/src/task/ModelBuilder.cpp
new file mode 100644
index 0000000..87373ca
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/ModelBuilder.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <tinyxml.h>
+
+#include <UniquePtr.h>
+
+#include "Log.h"
+#include "GenericFactory.h"
+#include "task/ModelBuilder.h"
+
+static const int MAX_NO_CHILDREN = 8;
+static const ModelBuilder::ChildInfo CASE_TABLE[] = {
+    { TaskGeneric::ETaskSetup, true },
+    { TaskGeneric::ETaskAction, true },
+    { TaskGeneric::ETaskSave, false }
+};
+static const ModelBuilder::ChildInfo SETUP_TABLE[] = {
+    { TaskGeneric::ETaskSound, false },
+    { TaskGeneric::ETaskProcess, false }
+};
+static const ModelBuilder::ChildInfo ACTION_TABLE[] = {
+    { TaskGeneric::ETaskSequential, true }
+};
+static const ModelBuilder::ChildInfo SEQUENTIAL_TABLE[] = {
+    { TaskGeneric::ETaskSequential, false },
+    { TaskGeneric::ETaskInput, false },
+    { TaskGeneric::ETaskOutput, false },
+    { TaskGeneric::ETaskProcess, false },
+    { TaskGeneric::ETaskMessage, false }
+};
+
+
+ModelBuilder::ParsingInfo ModelBuilder::mParsingTable[ModelBuilder::PARSING_TABLE_SIZE] = {
+    { "case", TaskGeneric::ETaskCase, CASE_TABLE,
+            sizeof(CASE_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+    { "setup", TaskGeneric::ETaskSetup, SETUP_TABLE,
+            sizeof(SETUP_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+    { "action", TaskGeneric::ETaskAction, ACTION_TABLE,
+            sizeof(ACTION_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+    { "sequential", TaskGeneric::ETaskSequential, SEQUENTIAL_TABLE,
+                sizeof(SEQUENTIAL_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+    { "process", TaskGeneric::ETaskProcess, NULL, 0 },
+    { "input", TaskGeneric::ETaskInput, NULL, 0 },
+    { "output", TaskGeneric::ETaskOutput, NULL, 0 },
+    { "sound", TaskGeneric::ETaskSound, NULL, 0 },
+    { "save", TaskGeneric::ETaskSave, NULL, 0 },
+    { "message", TaskGeneric::ETaskMessage, NULL, 0 }
+};
+
+
+ModelBuilder::ModelBuilder()
+    : mFactory(new GenericFactory())
+{
+
+}
+
+ModelBuilder::ModelBuilder(GenericFactory* factory)
+    : mFactory(factory)
+{
+
+}
+ModelBuilder::~ModelBuilder()
+{
+    delete mFactory;
+}
+
+TaskGeneric* ModelBuilder::parseTestDescriptionXml(const android::String8& xmlFileName,
+        bool caseOnly)
+{
+    TiXmlDocument doc(xmlFileName.string());
+    if (!doc.LoadFile()) {
+        LOGE("ModelBuilder::parseTestDescriptionXml cannot load file %s", xmlFileName.string());
+        return NULL;
+    }
+    const TiXmlElement* root;
+    if ((root = doc.FirstChildElement("case")) != NULL) {
+        return parseCase(*root);
+    } else if (!caseOnly && ((root = doc.FirstChildElement("batch")) != NULL)) {
+        return parseBatch(*root, xmlFileName);
+    } else {
+        LOGE("ModelBuilder::parseTestDescriptionXml wrong root element");
+        return NULL;
+    }
+}
+
+TaskGeneric* ModelBuilder::parseGeneric(const TiXmlElement& self, int tableIndex)
+{
+    TaskGeneric::TaskType typeSelf(mParsingTable[tableIndex].type);
+    int Nchildren = mParsingTable[tableIndex].Nchildren;
+    UniquePtr<TaskGeneric> taskSelf(mFactory->createTask(typeSelf));
+    if (taskSelf.get() == NULL) {
+        return NULL;
+    }
+    if (!parseAttributes(self, *taskSelf.get())) {
+        return NULL;
+    }
+    // copy mandatory flags, and will be cleared once the item is found
+    bool mandatoryAbsence[MAX_NO_CHILDREN];
+    const ModelBuilder::ChildInfo* childTable = mParsingTable[tableIndex].allowedChildren;
+    for (int i = 0; i < Nchildren; i++) {
+        mandatoryAbsence[i] = childTable[i].mandatory;
+    }
+
+    // handle children
+    const TiXmlElement* child = self.FirstChildElement();
+    while (child != NULL) {
+        TaskGeneric::TaskType childType(TaskGeneric::ETaskInvalid);
+        int i;
+        // check if type is valid
+        for (i = 0; i < PARSING_TABLE_SIZE; i++) {
+            if (strcmp(child->Value(), mParsingTable[i].name) == 0) {
+                break;
+            }
+        }
+        if (i == PARSING_TABLE_SIZE) {
+            LOGE("ModelBuilder::parseGeneric unknown element %s", child->Value());
+            return NULL;
+        }
+        childType = mParsingTable[i].type;
+        int j;
+        // check if the type is allowed as child
+        for (j = 0; j < Nchildren; j++) {
+            if (childTable[j].type == childType) {
+                if (childTable[j].mandatory) {
+                    mandatoryAbsence[j] = false;
+                }
+                break;
+            }
+        }
+        if (j == Nchildren) {
+            LOGE("ModelBuilder::parseGeneric unsupported child type %d for type %d", childType,
+                    typeSelf);
+            return NULL;
+        }
+        UniquePtr<TaskGeneric> taskChild(parseGeneric(*child, i));
+        if (taskChild.get() == NULL) {
+            LOGE("ModelBuilder::parseGeneric failed in parsing child type %d for type %d",
+                    childType, typeSelf);
+            return NULL;
+        }
+        if (!taskSelf.get()->addChild(taskChild.get())) {
+            LOGE("ModelBuilder::parseGeneric cannot add child type %d to type %d", childType,
+                    typeSelf);
+            return NULL;
+        }
+        TaskGeneric* donotuse = taskChild.release();
+
+        child = child->NextSiblingElement();
+    }
+    for (int i = 0; i < Nchildren; i++) {
+        if (mandatoryAbsence[i]) {
+            LOGE("ModelBuilder::parseGeneric mandatory child type %d not present in type %d",
+                    childTable[i].type, typeSelf);
+            return NULL;
+        }
+    }
+
+    return taskSelf.release();
+}
+
+
+TaskCase* ModelBuilder::parseCase(const TiXmlElement& root)
+{
+    // position 0 of mParsingTable should be "case"
+    return reinterpret_cast<TaskCase*>(parseGeneric(root, 0));
+}
+
+
+TaskBatch* ModelBuilder::parseBatch(const TiXmlElement& root, const android::String8& xmlFileName)
+{
+    UniquePtr<TaskBatch> batch(
+            reinterpret_cast<TaskBatch*>(mFactory->createTask(TaskGeneric::ETaskBatch)));
+    if (batch.get() == NULL) {
+        LOGE("ModelBuilder::handleBatch cannot create TaskBatch");
+        return NULL;
+    }
+    if (!parseAttributes(root, *batch.get())) {
+        return NULL;
+    }
+
+    const TiXmlElement* inc = root.FirstChildElement("include");
+    if (inc == NULL) {
+        LOGE("ModelBuilder::handleBatch no include inside batch");
+        return NULL;
+    }
+    android::String8 path = xmlFileName.getPathDir();
+
+    UniquePtr<TaskCase> testCase;
+    int i = 0;
+    while (1) {
+        if (inc == NULL) {
+            break;
+        }
+        if (strcmp(inc->Value(),"include") != 0) {
+            LOGE("ModelBuilder::handleBatch invalid element %s", inc->Value());
+        }
+        testCase.reset(parseInclude(*inc, path));
+        if (testCase.get() == NULL) {
+            LOGE("ModelBuilder::handleBatch cannot create test case from include");
+            return NULL;
+        }
+        if (!batch.get()->addChild(testCase.get())) {
+            return NULL;
+        }
+        TaskGeneric* donotuse = testCase.release(); // parent will take care of destruction.
+        inc = inc->NextSiblingElement();
+        i++;
+    }
+    if (i == 0) {
+        // at least one include should exist.
+        LOGE("ModelBuilder::handleBatch no include elements");
+        return NULL;
+    }
+
+    return batch.release();
+}
+
+TaskCase* ModelBuilder::parseInclude(const TiXmlElement& elem, const android::String8& path)
+{
+    const char* fileName = elem.Attribute("file");
+    if (fileName == NULL) {
+        LOGE("ModelBuilder::handleBatch no include elements");
+        return NULL;
+    }
+    android::String8 incFile = path;
+    incFile.appendPath(fileName);
+
+    // again no dynamic_cast intentionally
+    return reinterpret_cast<TaskCase*>(parseTestDescriptionXml(incFile, true));
+}
+
+bool ModelBuilder::parseAttributes(const TiXmlElement& elem, TaskGeneric& task)
+{
+    const TiXmlAttribute* attr = elem.FirstAttribute();
+    while (1) {
+        if (attr == NULL) {
+            break;
+        }
+        android::String8 name(attr->Name());
+        android::String8 value(attr->Value());
+        if (!task.parseAttribute(name, value)) {
+            LOGE("ModelBuilder::parseAttributes cannot parse attribute %s:%s for task type %d",
+                    attr->Name(), attr->Value(), task.getType());
+            return false;
+        }
+        attr = attr->Next();
+    }
+    return true;
+}
diff --git a/suite/audio_quality/lib/src/task/TaskAsync.cpp b/suite/audio_quality/lib/src/task/TaskAsync.cpp
new file mode 100644
index 0000000..4121a5e
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskAsync.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <stdlib.h>
+#include "Log.h"
+#include "StringUtil.h"
+#include "task/TaskAll.h"
+
+TaskAsync::TaskAsync(TaskType type)
+    : TaskGeneric(type),
+      mVolume(-1),
+      mDeviceType(EDeviceHost),
+      mMode(AudioHardware::EModeVoice),
+      mAsynchronous(false)
+{
+    // nothing to do
+}
+
+TaskAsync::~TaskAsync()
+{
+
+}
+
+TaskGeneric::ExecutionResult TaskAsync::run()
+{
+    // id is mandatory
+    if (mId.length() == 0) {
+        LOGE(" TaskAsync::run no id attribute");
+        return TaskGeneric::EResultError;
+    }
+    TaskGeneric::ExecutionResult result = start();
+    if (result == TaskGeneric::EResultOK) {
+        if (!isAsynchronous()) {
+            return complete();
+        } else {
+            if (!getParentSequential()->queueAsyncTask(const_cast<TaskAsync*>(this))) {
+                LOGE("TaskAsync::run queueAsyncTask failed");
+                return TaskGeneric::EResultError;
+            }
+        }
+    }
+    return result;
+}
+
+bool TaskAsync::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    bool result = true;
+    if (StringUtil::compare(name, "id") == 0) {
+        mId.append(value);
+    } else if (StringUtil::compare(name, "gain") == 0) {
+        mVolume = atoi(value.string());
+        if ((mVolume < 1) || (mVolume > 100)) {
+            LOGE("TaskGeneric::parseAttribute gain out of range %d", mVolume);
+            return false;
+        }
+    } else if (StringUtil::compare(name, "sync") == 0) {
+        if (StringUtil::compare(value, "start") == 0) { // async
+            makeAsynchronous();
+        }
+    } else if (StringUtil::compare(name, "device") == 0) {
+        if (StringUtil::compare(value, "host") == 0) {
+            mDeviceType = EDeviceHost;
+        } else if (StringUtil::compare(value, "DUT") == 0) {
+            mDeviceType = EDeviceDUT;
+        } else {
+            return false;
+        }
+    } else if (StringUtil::compare(name, "mode") == 0) {
+            if (StringUtil::compare(value, "voice") == 0) {
+                mMode = AudioHardware::EModeVoice;
+            } else if (StringUtil::compare(value, "music") == 0) {
+                mMode = AudioHardware::EModeMusic;
+            } else {
+                return false;
+            }
+    } else {
+        result = TaskGeneric::parseAttribute(name, value);
+    }
+    return result;
+}
+
+TaskSequential* TaskAsync::getParentSequential()
+{
+    ASSERT(getParent()->getType() == TaskGeneric::ETaskSequential);
+    return reinterpret_cast<TaskSequential*>(getParent());
+}
+
diff --git a/suite/audio_quality/lib/src/task/TaskBatch.cpp b/suite/audio_quality/lib/src/task/TaskBatch.cpp
new file mode 100644
index 0000000..f8c77fe
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskBatch.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "Log.h"
+#include "Report.h"
+
+#include "task/TaskBatch.h"
+
+static const android::String8 STR_NAME("name");
+static const android::String8 STR_VERSION("version");
+static const android::String8 STR_DESCRIPTION("description");
+
+TaskBatch::TaskBatch()
+    :TaskGeneric(TaskGeneric::ETaskBatch)
+{
+    const android::String8* list[] = {&STR_NAME, &STR_VERSION, &STR_DESCRIPTION, NULL};
+    registerSupportedStringAttributes(list);
+}
+
+TaskBatch::~TaskBatch()
+{
+
+}
+
+bool TaskBatch::addChild(TaskGeneric* child)
+{
+    if (child->getType() != TaskGeneric::ETaskCase) {
+        LOGE("TaskBatch::addChild wrong child type %d", child->getType());
+        return false;
+    }
+    return TaskGeneric::addChild(child);
+}
+
+bool runAlways(TaskGeneric* child, void* data)
+{
+    child->run();
+    return true;
+}
+
+TaskGeneric::ExecutionResult TaskBatch::run()
+{
+    android::String8 name;
+    android::String8 version;
+
+    if (!findStringAttribute(STR_NAME, name) || !findStringAttribute(STR_VERSION, version)) {
+        LOGW("TaskBatch::run no name or version information");
+    }
+    Report::Instance()->printf("= Test batch %s version %s started. =", name.string(),
+            version.string());
+    bool result = TaskGeneric::forEachChild(runAlways, NULL);
+    Report::Instance()->printf("= Finished Test batch =");
+    return TaskGeneric::EResultOK;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskCase.cpp b/suite/audio_quality/lib/src/task/TaskCase.cpp
new file mode 100644
index 0000000..9cbc6c8
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskCase.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <sys/types.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "Log.h"
+#include "audio/RemoteAudio.h"
+#include "ClientImpl.h"
+#include "Report.h"
+#include "Settings.h"
+#include "StringUtil.h"
+#include "task/TaskCase.h"
+
+static const android::String8 STR_NAME("name");
+static const android::String8 STR_VERSION("version");
+static const android::String8 STR_DESCRIPTION("description");
+
+TaskCase::TaskCase()
+    : TaskGeneric(TaskGeneric::ETaskCase),
+      mClient(NULL)
+{
+    const android::String8* list[] = {&STR_NAME, &STR_VERSION, &STR_DESCRIPTION, NULL};
+    registerSupportedStringAttributes(list);
+}
+
+TaskCase::~TaskCase()
+{
+    delete mClient;
+}
+
+bool TaskCase::getCaseName(android::String8& name)
+{
+    if (!findStringAttribute(STR_NAME, name)) {
+        LOGW("TaskCase no name");
+        return false;
+    }
+    return true;
+}
+
+bool TaskCase::addChild(TaskGeneric* child)
+{
+    if ((child->getType() != TaskGeneric::ETaskSetup)
+            &&  (child->getType() != TaskGeneric::ETaskAction)
+            &&  (child->getType() != TaskGeneric::ETaskSave)) {
+        LOGE("TestCase::addChild wrong child type %d", child->getType());
+        return false;
+    }
+    return TaskGeneric::addChild(child);
+}
+
+template <typename T> bool registerGeneric(
+        typename std::map<android::String8, T>& map,
+        const android::String8& name, T& data)
+{
+    typename std::map<android::String8, T>::iterator it;
+    it = map.find(name);
+    if (it != map.end()) {
+        LOGV("registerGeneric key %s already registered", name.string());
+        return false;
+    }
+    LOGD("registerGeneric registered key %s", name.string());
+    map[name] = data;
+    return true;
+}
+
+template <typename T> bool findGeneric(typename std::map<android::String8, T>& map,
+        const android::String8& name, T& data)
+{
+    LOGD("findGeneric key %s", name.string());
+    typename std::map<android::String8, T>::iterator it;
+    it = map.find(name);
+    if (it == map.end()) {
+        return false;
+    }
+    data = it->second;
+    return true;
+}
+
+template <typename T> bool updateGeneric(typename std::map<android::String8, T>& map,
+        const android::String8& name, T& data)
+{
+    LOGD("updateGeneric key %s", name.string());
+    typename std::map<android::String8, T>::iterator it;
+    it = map.find(name);
+    if (it == map.end()) {
+        return false;
+    }
+    it->second = data;
+    return true;
+}
+
+// return all the matches for the given regular expression.
+// name string and the data itself is copied.
+template <typename T> typename std::list<std::pair<android::String8, T> >* findAllGeneric(
+        typename std::map<android::String8, T>& map, const char* re)
+{
+    regex_t regex;
+    if (regcomp(&regex, re, REG_EXTENDED | REG_NOSUB) != 0) {
+        LOGE("regcomp failed");
+        return NULL;
+    }
+    typename std::map<android::String8, T>::iterator it;
+    typename std::list<std::pair<android::String8, T> >* list = NULL;
+    for (it = map.begin(); it != map.end(); it++) {
+        if (regexec(&regex, it->first, 0, NULL, 0) == 0) {
+            if (list == NULL) { // create only when found
+                list = new std::list<std::pair<android::String8, T> >();
+                if (list == NULL) {
+                    regfree(&regex);
+                    return NULL;
+                }
+            }
+            typename std::pair<android::String8, T> match(it->first, it->second);
+            list->push_back(match);
+        }
+    }
+    regfree(&regex);
+    return list;
+}
+
+
+bool TaskCase::registerBuffer(const android::String8& orig, android::sp<Buffer>& buffer)
+{
+    android::String8 translated;
+    if (!translateVarName(orig, translated)) {
+        return false;
+    }
+    return registerGeneric<android::sp<Buffer> >(mBufferList, translated, buffer);
+}
+
+bool TaskCase::updateBuffer(const android::String8& orig, android::sp<Buffer>& buffer)
+{
+    android::String8 translated;
+    if (!translateVarName(orig, translated)) {
+        return false;
+    }
+    return updateGeneric<android::sp<Buffer> >(mBufferList, translated, buffer);
+}
+
+android::sp<Buffer> TaskCase::findBuffer(const android::String8& orig)
+{
+    android::String8 translated;
+    android::sp<Buffer> result;
+    if (!translateVarName(orig, translated)) {
+        return result;
+    }
+    findGeneric<android::sp<Buffer> >(mBufferList, translated, result);
+    return result;
+}
+
+std::list<TaskCase::BufferPair>* TaskCase::findAllBuffers(const android::String8& re)
+{
+    android::String8 translated;
+    if (!translateVarName(re, translated)) {
+        return NULL;
+    }
+    return findAllGeneric<android::sp<Buffer> >(mBufferList, translated.string());
+}
+
+
+bool TaskCase::registerValue(const android::String8& orig, Value& val)
+{
+    android::String8 translated;
+    if (!translateVarName(orig, translated)) {
+        return false;
+    }
+    LOGD("str %x", translated.string());
+    return registerGeneric<Value>(mValueList, translated, val);
+}
+
+bool TaskCase::updateValue(const android::String8& orig, Value& val)
+{
+    android::String8 translated;
+    if (!translateVarName(orig, translated)) {
+        return false;
+    }
+    return updateGeneric<Value>(mValueList, translated, val);
+}
+
+bool TaskCase::findValue(const android::String8& orig, Value& val)
+{
+    android::String8 translated;
+    if (!translateVarName(orig, translated)) {
+        return false;
+    }
+    return findGeneric<Value>(mValueList, translated, val);
+}
+
+std::list<TaskCase::ValuePair>* TaskCase::findAllValues(const android::String8& re)
+{
+    android::String8 translated;
+    if (!translateVarName(re, translated)) {
+        return NULL;
+    }
+    return findAllGeneric<Value>(mValueList, translated.string());
+}
+
+bool TaskCase::registerIndex(const android::String8& name, int value)
+{
+    return registerGeneric<int>(mIndexList, name, value);
+}
+
+bool TaskCase::updateIndex(const android::String8& name, int value)
+{
+    return updateGeneric<int>(mIndexList, name, value);
+}
+
+bool TaskCase::findIndex(const android::String8& name, int& val)
+{
+    return findGeneric<int>(mIndexList, name, val);
+}
+
+std::list<TaskCase::IndexPair>* TaskCase::findAllIndices(const android::String8& re)
+{
+    android::String8 translated;
+    if (!translateVarName(re, translated)) {
+        return NULL;
+    }
+    return findAllGeneric<int>(mIndexList, translated.string());
+}
+
+bool TaskCase::translateVarName(const android::String8& orig, android::String8& translated)
+{
+    const char* src = orig.string();
+    const int nmatch = 2;
+    regmatch_t pmatch[nmatch];
+    regex_t re;
+    size_t strStart = 0;
+
+    if (regcomp(&re, "[a-z0-9_]*[$]([a-z0-9]+)[_]*", REG_EXTENDED) != 0) {
+        LOGE("regcomp failed");
+        return false;
+    }
+    bool result = false;
+    size_t matchStart = 0;
+    size_t matchEnd = 0;
+    while (regexec(&re, src, nmatch, pmatch, 0) == 0) {
+        matchStart = strStart + pmatch[1].rm_so;
+        matchEnd = strStart + pmatch[1].rm_eo;
+        translated.append(StringUtil::substr(orig, strStart, pmatch[1].rm_so - 1)); //-1 for $
+        android::String8 indexName;
+        indexName.append(StringUtil::substr(orig, matchStart, matchEnd - matchStart));
+        int val;
+        if (!findIndex(indexName, val)) {
+            LOGE("TaskCase::translateVarName no index with name %s", indexName.string());
+            regfree(&re);
+            return false;
+        }
+        translated.appendFormat("%d", val);
+        LOGD("match found strStart %d, matchStart %d, matchEnd %d, converted str %s",
+                strStart, matchStart, matchEnd, translated.string());
+        src += pmatch[1].rm_eo;
+        strStart += pmatch[1].rm_eo;
+    }
+    if (matchEnd < orig.length()) {
+        //LOGD("%d %d", matchEnd, orig.length());
+        translated.append(StringUtil::substr(orig, matchEnd, orig.length() - matchEnd));
+    }
+    LOGD("translated str %s to %s", orig.string(), translated.string());
+    regfree(&re);
+    return true;
+}
+
+android::sp<RemoteAudio>& TaskCase::getRemoteAudio()
+{
+    if (mClient == NULL) {
+        mClient = new ClientImpl();
+        ASSERT(mClient->init(Settings::Instance()->getSetting(Settings::EADB)));
+    }
+    return mClient->getAudio();
+}
+
+void TaskCase::releaseRemoteAudio()
+{
+    delete mClient;
+    mClient = NULL;
+}
+
+TaskGeneric::ExecutionResult TaskCase::run()
+{
+    android::String8 name;
+    android::String8 version;
+    //LOGI("str %d, %d", strlen(STR_NAME), strlen(STR_VERSION));
+    if (!findStringAttribute(STR_NAME, name) || !findStringAttribute(STR_VERSION, version)) {
+        LOGW("TaskCase::run no name or version information");
+    }
+    Report::Instance()->printf("== Test case %s version %s started ==", name.string(),
+            version.string());
+    std::list<TaskGeneric*>::iterator i = getChildren().begin();
+    std::list<TaskGeneric*>::iterator end = getChildren().end();
+    TaskGeneric* setup = *i;
+    i++;
+    TaskGeneric* action = *i;
+    i++;
+    TaskGeneric* save = (i == end)? NULL : *i;
+    if (save == NULL) {
+        LOGW("No save stage in test case");
+    }
+    bool testPassed = true;
+    TaskGeneric::ExecutionResult result = setup->run();
+    TaskGeneric::ExecutionResult resultAction(TaskGeneric::EResultOK);
+    if (result != TaskGeneric::EResultOK) {
+        Report::Instance()->printf("== setup stage failed %d ==", result);
+        testPassed = false;
+    } else {
+        resultAction = action->run();
+        if (resultAction != TaskGeneric::EResultPass) {
+            Report::Instance()->printf("== action stage failed %d ==", resultAction);
+            testPassed = false;
+        }
+        // save done even for failure if possible
+        if (save != NULL) {
+            result = save->run();
+        }
+        if (result != TaskGeneric::EResultOK) {
+            Report::Instance()->printf("== save stage failed %d ==", result);
+            testPassed = false;
+        }
+    }
+    if (testPassed) {
+        result = TaskGeneric::EResultPass;
+        Report::Instance()->printf("== Case %s Passed ==", name.string());
+        Report::Instance()->addCasePassed(name);
+    } else {
+        if (resultAction != TaskGeneric::EResultOK) {
+            result = resultAction;
+        }
+        Report::Instance()->printf("== Case %s Failed ==", name.string());
+        Report::Instance()->addCaseFailed(name);
+    }
+    // release remote audio for other cases to use
+    releaseRemoteAudio();
+    return result;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskGeneric.cpp b/suite/audio_quality/lib/src/task/TaskGeneric.cpp
new file mode 100644
index 0000000..7abd137
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskGeneric.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "Log.h"
+
+#include "task/TaskAll.h"
+
+
+TaskGeneric::TaskGeneric(TaskType type):
+    mType(type),
+    mParent(NULL)
+{
+
+}
+
+bool deleteChildInstance(TaskGeneric* child, void* /*data*/)
+{
+    delete child;
+    return true;
+}
+
+TaskGeneric::~TaskGeneric()
+{
+    forEachChild(deleteChildInstance, NULL);
+    //mChildren.clear();
+}
+
+bool TaskGeneric::addChild(TaskGeneric* child)
+{
+    mChildren.push_back(child);
+    child->setParent(this);
+    return true;
+}
+
+bool TaskGeneric::forEachChild(bool (*runForEachChild)(TaskGeneric* child, void* data), void* data)
+{
+    std::list<TaskGeneric*>::iterator i = mChildren.begin();
+    std::list<TaskGeneric*>::iterator end = mChildren.end();
+    for (; i != end; i++) {
+        if (!(*runForEachChild)(*i, data)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+TaskGeneric* TaskGeneric::getParent()
+{
+    return mParent;
+}
+
+TaskCase* TaskGeneric::getTestCase()
+{
+    TaskGeneric* task = this;
+
+    while (task != NULL) {
+        if (task->getType() == ETaskCase) {
+            // do not use dynamic_cast intentionally
+            return reinterpret_cast<TaskCase*>(task);
+        }
+        task = task->getParent();
+    }
+    LOGE("TaskGeneric::getTestCase no TaskCase found!");
+    return NULL;
+}
+
+void TaskGeneric::setParent(TaskGeneric* parent)
+{
+    LOGD("TaskGeneric::setParent self %x, parent %x", this, parent);
+    mParent = parent;
+}
+
+bool runChild(TaskGeneric* child, void* data)
+{
+    TaskGeneric::ExecutionResult* result = reinterpret_cast<TaskGeneric::ExecutionResult*>(data);
+    *result = child->run();
+    if (*result != TaskGeneric::EResultOK) {
+        LOGE("child type %d returned %d", child->getType(), *result);
+        return false;
+    }
+    return true;
+}
+
+TaskGeneric::ExecutionResult TaskGeneric::run()
+{
+    ExecutionResult result = EResultOK;
+    forEachChild(runChild, &result);
+    return result;
+}
+
+bool TaskGeneric::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    // default implementation only handles registered string attributes
+    if (!addStringAttribute(name, value)) {
+        LOGE("parseAttribute unknown attribute %s %s for type %d",
+                name.string(), value.string(), getType());
+        return false;
+    }
+    return true;
+}
+
+
+void TaskGeneric::registerSupportedStringAttributes(const android::String8* keys[])
+{
+    int i = 0;
+    while (keys[i] != NULL) {
+        mAllowedStringAttributes.insert(*keys[i]);
+        i++;
+    }
+}
+
+bool TaskGeneric::addStringAttribute(const android::String8& key, const android::String8& value)
+{
+    std::set<android::String8, android::String8>::iterator it = mAllowedStringAttributes.find(key);
+    if (it == mAllowedStringAttributes.end()) {
+        return false; // not allowed
+    }
+    mStringAttributes[key] = value;
+    return true;
+}
+
+bool TaskGeneric::findStringAttribute(const android::String8& key, android::String8& value)
+{
+    std::map<android::String8, android::String8>::iterator it = mStringAttributes.find(key);
+    if (it == mStringAttributes.end()) {
+        return false; // not found
+    }
+    value = it->second;
+    return true;
+}
+
diff --git a/suite/audio_quality/lib/src/task/TaskInput.cpp b/suite/audio_quality/lib/src/task/TaskInput.cpp
new file mode 100644
index 0000000..e5b4b06
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskInput.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <stdlib.h>
+
+#include "Log.h"
+#include "audio/AudioHardware.h"
+#include "task/TaskCase.h"
+#include "task/TaskInput.h"
+
+TaskInput::TaskInput()
+    : TaskAsync(TaskGeneric::ETaskInput),
+      mRecordingTimeInMs(0)
+{
+
+}
+
+TaskInput::~TaskInput()
+{
+
+}
+
+bool TaskInput::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    if (strcmp(name, "time") == 0) {
+        mRecordingTimeInMs = atoi(value);
+        if (mRecordingTimeInMs < 0) {
+            LOGE("TaskInput::parseAttribute invalid recording time %d", mRecordingTimeInMs);
+            return false;
+        }
+        return true;
+    }
+    return TaskAsync::parseAttribute(name, value);
+}
+
+TaskGeneric::ExecutionResult TaskInput::start()
+{
+    bool localDevice = (mDeviceType == TaskAsync::EDeviceHost);
+    android::sp<AudioHardware> hw = AudioHardware::createAudioHw(localDevice, false,
+            getTestCase());
+    if (hw.get() == NULL) {
+        LOGE("createAudioHw failed");
+        return TaskGeneric::EResultError;
+    }
+    // TODO support stereo mode in local later
+    //     for now, local is captured in stereo, and it is stored to mono
+    //     by keeping only channel 1.
+    // local : stereo only, remote : mono only
+    size_t bufferSize = mRecordingTimeInMs * AudioHardware::ESampleRate_44100 / 1000 *
+            (localDevice ? 4 : 2);
+    android::sp<Buffer> buffer(new Buffer(bufferSize, bufferSize, localDevice));
+    if (buffer.get() == NULL) {
+        LOGE("buffer alloc failed");
+        return TaskGeneric::EResultError;
+    }
+    if (!hw->prepare(AudioHardware::ESampleRate_44100, mVolume, mMode)) {
+        LOGE("prepare failed");
+        return TaskGeneric::EResultError;
+    }
+    if (!hw->startPlaybackOrRecord(buffer)) {
+        LOGE("record failed");
+        return TaskGeneric::EResultError;
+    }
+    // now store sp
+    mHw = hw;
+    mBuffer = buffer;
+    return TaskGeneric::EResultOK;
+}
+
+TaskGeneric::ExecutionResult TaskInput::complete()
+{
+    bool result = mHw->waitForCompletion();
+    mHw->stopPlaybackOrRecord();
+    mHw.clear();
+    if (!result) {
+        LOGE("waitForComletion failed");
+        return TaskGeneric::EResultError;
+    }
+    // TODO: need to keep stereo for local if in stereo mode
+    // For now, convert to mono if it is stereo
+    if (mBuffer->isStereo()) {
+        mBuffer->changeToMono(Buffer::EKeepCh0);
+    }
+    if (!getTestCase()->registerBuffer(mId, mBuffer)) {
+        if (!getTestCase()->updateBuffer(mId, mBuffer)) {
+            LOGE("cannot register/update buffer %s", mId.string());
+            return TaskGeneric::EResultError;
+        }
+    }
+    return TaskGeneric::EResultOK;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskMessage.cpp b/suite/audio_quality/lib/src/task/TaskMessage.cpp
new file mode 100644
index 0000000..4241d2e
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskMessage.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "task/TaskMessage.h"
+
+
+TaskMessage::TaskMessage()
+    : TaskGeneric(TaskGeneric::ETaskMessage)
+{}
+TaskMessage::~TaskMessage()
+{
+
+}
+TaskGeneric::ExecutionResult TaskMessage::run()
+{
+    //TODO
+    return TaskGeneric::EResultError;
+}
+bool TaskMessage::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    //TODO
+    return false;
+}
diff --git a/suite/audio_quality/lib/src/task/TaskOutput.cpp b/suite/audio_quality/lib/src/task/TaskOutput.cpp
new file mode 100644
index 0000000..ce3a7d8
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskOutput.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "task/TaskCase.h"
+#include "StringUtil.h"
+#include "task/TaskOutput.h"
+#include "audio/AudioRemote.h"
+#include "audio/RemoteAudio.h"
+
+
+TaskOutput::TaskOutput()
+    : TaskAsync(TaskGeneric::ETaskOutput),
+      mWaitForCompletion(false)
+{
+
+}
+
+TaskOutput::~TaskOutput()
+{
+
+}
+bool TaskOutput::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    if (StringUtil::compare(name, "waitforcompletion") == 0) {
+        if (StringUtil::compare(value, "1") == 0) {
+            mWaitForCompletion = true;
+        }
+        return true;
+    }
+    return TaskAsync::parseAttribute(name, value);
+}
+TaskGeneric::ExecutionResult TaskOutput::start()
+{
+    bool localDevice = (mDeviceType == TaskAsync::EDeviceHost);
+    android::sp<AudioHardware> hw = AudioHardware::createAudioHw(localDevice, true, getTestCase());
+    if (hw.get() == NULL) {
+        LOGE("cannot create Audio HW");
+        return TaskGeneric::EResultError;
+    }
+    if (!hw->prepare(AudioHardware::ESampleRate_44100, mVolume, mMode)) {
+        LOGE("prepare failed");
+        return TaskGeneric::EResultError;
+    }
+    if (localDevice) {
+        android::sp<Buffer> buffer;
+        buffer = getTestCase()->findBuffer(mId);
+        if (buffer.get() == NULL) {
+            LOGE("cannot find buffer %s", mId.string());
+            return TaskGeneric::EResultError;
+        }
+        buffer->restart(); // reset to play from beginning
+
+        if (!hw->startPlaybackOrRecord(buffer)) {
+            LOGE("play failed");
+            return TaskGeneric::EResultError;
+        }
+    } else {
+        int id = getTestCase()->getRemoteAudio()->getDataId(mId);
+        if (id < 0) {
+            return TaskGeneric::EResultError;
+        }
+        AudioRemotePlayback* remote = reinterpret_cast<AudioRemotePlayback*>(hw.get());
+        if (!remote->startPlaybackForRemoteData(id, false)) { // mono always
+            return TaskGeneric::EResultError;
+        }
+    }
+    // now store sp
+    mHw = hw;
+
+    return TaskGeneric::EResultOK;
+}
+
+TaskGeneric::ExecutionResult TaskOutput::complete()
+{
+    bool result = true;
+    if (mWaitForCompletion) {
+        result = mHw->waitForCompletion();
+    }
+    mHw->stopPlaybackOrRecord();
+    mHw.clear();
+    if (!result) {
+        LOGE("waitForCompletion failed");
+        return TaskGeneric::EResultError;
+    }
+    return TaskGeneric::EResultOK;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskProcess.cpp b/suite/audio_quality/lib/src/task/TaskProcess.cpp
new file mode 100644
index 0000000..f1e47af
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskProcess.cpp
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#include "Log.h"
+#include "StringUtil.h"
+#include "task/TaskProcess.h"
+#include "SignalProcessingImpl.h"
+
+TaskProcess::TaskProcess()
+    : TaskGeneric(TaskGeneric::ETaskProcess)
+{
+
+}
+
+TaskProcess::~TaskProcess()
+{
+}
+
+TaskGeneric::ExecutionResult TaskProcess::run()
+{
+    if (mType == EBuiltin) {
+        return doRun(true);
+    } else {
+        if (mSp.get() == NULL) {
+            mSp.reset(new SignalProcessingImpl());
+            if (!mSp->init(SignalProcessingImpl::MAIN_PROCESSING_SCRIPT)) {
+                mSp.reset(NULL);
+                return TaskGeneric::EResultError;
+            }
+        }
+        return doRun(false);
+    }
+}
+
+// Allocate Buffers and Values to pass to builtin functions
+bool TaskProcess::prepareParams(std::vector<TaskProcess::Param>& list,
+        const bool* paramTypes,
+        UniquePtr<void_ptr, DefaultDelete<void_ptr[]> > & ptrs,
+        UniquePtr<UniqueValue, DefaultDelete<UniqueValue[]> > & values,
+        UniquePtr<UniqueBuffer, DefaultDelete<UniqueBuffer[]> > & buffers,
+        bool isInput)
+{
+    size_t N = list.size();
+
+    LOGD("TaskProcess::prepareParams N = %d", N);
+    ptrs.reset(new void_ptr[N]);
+    if (ptrs.get() == NULL) {
+        LOGE("alloc failed");
+        return false;
+    }
+    // set to NULL to detect illegal access
+    bzero(ptrs.get(), N * sizeof(void_ptr));
+    values.reset(new UniqueValue[N]);
+    if (values.get() == NULL) {
+        LOGE("alloc failed");
+        return false;
+    }
+    buffers.reset(new UniqueBuffer[N]);
+    if (buffers.get() == NULL) {
+        LOGE("alloc failed");
+        return false;
+    }
+
+    void_ptr* voidPtrs = ptrs.get();
+    UniqueValue* valuesPtr = values.get();
+    UniqueBuffer* buffersPtr = buffers.get();
+    for (size_t i = 0; i < N; i++) {
+        if ((paramTypes != NULL) && paramTypes[i] && (list[i].getType() != EId)) {
+            LOGE("mismatching types %d %d", paramTypes[i], list[i].getType());
+            return false;
+        }
+        if ((paramTypes != NULL) && !paramTypes[i] && (list[i].getType() == EId)) {
+            LOGE("mismatching types %d %d", paramTypes[i], list[i].getType());
+            return false;
+        }
+        switch(list[i].getType()) {
+        case EId: {
+            UniquePtr<android::sp<Buffer> > buffer(new android::sp<Buffer>());
+            if (buffer.get() == NULL) {
+                LOGE("alloc failed");
+                return false;
+            }
+            if (isInput) {
+                *(buffer.get()) = getTestCase()->findBuffer(list[i].getParamString());
+                if (buffer.get()->get() == NULL) {
+                    LOGE("find failed");
+                    return false;
+                }
+                LOGD("input buffer len %d stereo %d", (*buffer.get())->getSize(),
+                        (*buffer.get())->isStereo());
+            }
+            buffersPtr[i].reset(buffer.release());
+            voidPtrs[i] = buffersPtr[i].get();
+        }
+        break;
+        case EVal: {
+            valuesPtr[i].reset(new TaskCase::Value());
+            if (isInput) {
+                if (!getTestCase()->findValue(list[i].getParamString(), *(valuesPtr[i].get()))) {
+                    LOGE("find %s failed", list[i].getParamString().string());
+                    return false;
+                }
+            }
+            voidPtrs[i] = valuesPtr[i].get();
+        }
+        break;
+        case EConst: {
+            if (!isInput) {
+                LOGE("const for output");
+                return false;
+            }
+            voidPtrs[i] = list[i].getValuePtr();
+
+            if (list[i].getValue().getType() == TaskCase::Value::ETypeDouble) {
+                LOGD(" %f", list[i].getValue().getDouble());
+            } else {
+                LOGD(" %lld", list[i].getValue().getInt64());
+            }
+        }
+        break;
+        }
+        LOGD("TaskProcess::prepareParams %d-th, const 0x%x", i, voidPtrs[i]);
+    }
+    return true;
+}
+
+// run builtin function by searching BuiltinProcessing::BUINTIN_FN_TABLE
+TaskGeneric::ExecutionResult TaskProcess::doRun(bool builtIn)
+{
+    BuiltinProcessing::BuiltinInfo* info = NULL;
+    if (builtIn) {
+        for (int i = 0; i < BuiltinProcessing::N_BUILTIN_FNS; i++) {
+            if (StringUtil::compare(mName, BuiltinProcessing::BUINTIN_FN_TABLE[i].mName) == 0) {
+                info = &BuiltinProcessing::BUINTIN_FN_TABLE[i];
+                break;
+            }
+        }
+        if (info == NULL) {
+            LOGE("TaskProcess::runBuiltin no match for %s", mName.string());
+            return TaskGeneric::EResultError;
+        }
+        if (mInput.size() != info->mNInput) {
+            LOGE("TaskProcess::runBuiltin size mismatch %d vs %d", mInput.size(), info->mNInput);
+            return TaskGeneric::EResultError;
+        }
+        if (mOutput.size() != info->mNOutput) {
+            LOGE("TaskProcess::runBuiltin size mismatch %d vs %d", mOutput.size(), info->mNOutput);
+            return TaskGeneric::EResultError;
+        }
+    }
+    // This is for passing to builtin fns. Just void pts will be cleared in exit
+    UniquePtr<void_ptr, DefaultDelete<void_ptr[]> > inputs;
+    // This is for holding Value instances. Will be destroyed in exit
+    UniquePtr<UniqueValue, DefaultDelete<UniqueValue[]> > inputValues;
+    // This is for holding android::sp<Buffer>. Buffer itself is from the global map.
+    UniquePtr<UniqueBuffer, DefaultDelete<UniqueBuffer[]> > inputBuffers;
+
+    UniquePtr<void_ptr, DefaultDelete<void_ptr[]> > outputs;
+    // Value is created here. Builtin function just need to set it.
+    UniquePtr<UniqueValue, DefaultDelete<UniqueValue[]> > outputValues;
+    // Buffer itself should be allocated by the builtin function itself.
+    UniquePtr<UniqueBuffer, DefaultDelete<UniqueBuffer[]> > outputBuffers;
+
+    if (!prepareParams(mInput, builtIn ? info->mInputTypes : NULL, inputs, inputValues,
+            inputBuffers, true)) {
+        return TaskGeneric::EResultError;
+    }
+
+    if (!prepareParams(mOutput, builtIn ? info->mOutputTypes : NULL, outputs, outputValues,
+            outputBuffers, false)) {
+        return TaskGeneric::EResultError;
+    }
+
+    TaskGeneric::ExecutionResult result;
+    if (builtIn) {
+        result = (mBuiltin.*(info->mFunction))(inputs.get(), outputs.get());
+    } else {
+        UniquePtr<bool, DefaultDelete<bool[]> > inputTypes(new bool[mInput.size()]);
+        for (size_t i = 0; i < mInput.size(); i++) {
+            (inputTypes.get())[i] = mInput[i].isIdType();
+        }
+        UniquePtr<bool, DefaultDelete<bool[]> > outputTypes(new bool[mOutput.size()]);
+        for (size_t i = 0; i < mOutput.size(); i++) {
+            (outputTypes.get())[i] = mOutput[i].isIdType();
+        }
+        result = mSp->run( mName,
+                mInput.size(), inputTypes.get(), inputs.get(),
+                mOutput.size(), outputTypes.get(), outputs.get());
+    }
+    if ((result == TaskGeneric::EResultOK) || (result == TaskGeneric::EResultFail)
+            || (result == TaskGeneric::EResultPass)) {
+        // try to save result
+        bool saveResultFailed = false;
+        for (size_t i = 0; i < mOutput.size(); i++) {
+            if (mOutput[i].isIdType()) { // Buffer
+                android::sp<Buffer>* bufferp =
+                        reinterpret_cast<android::sp<Buffer>*>((outputs.get())[i]);
+                if (!getTestCase()->registerBuffer(mOutput[i].getParamString(), *bufferp)) {
+                    // maybe already there, try update
+                    if (!getTestCase()->updateBuffer(mOutput[i].getParamString(), *bufferp)) {
+                        LOGE("cannot register / update %d-th output Buffer for builtin fn %s",
+                                i, mName.string());
+                        saveResultFailed = true; // mark failure, but continue
+                    }
+                }
+            } else { // Value
+                TaskCase::Value* valuep =
+                        reinterpret_cast<TaskCase::Value*>((outputs.get())[i]);
+                if (!getTestCase()->registerValue(mOutput[i].getParamString(), *valuep)) {
+                    if (!getTestCase()->updateValue(mOutput[i].getParamString(), *valuep)) {
+                        LOGE("cannot register / update %d-th output Value for builtin fn %s",
+                                i, mName.string());
+                        saveResultFailed = true; // mark failure, but continue
+                    }
+                }
+            }
+        }
+        if (saveResultFailed) {
+            LOGE("TaskProcess::runBuiltin cannot save result");
+            return TaskGeneric::EResultError;
+        }
+    }
+    LOGV("TaskProcess::runBuiltin return %d", result);
+    return result;
+}
+
+bool TaskProcess::parseParams(std::vector<TaskProcess::Param>& list, const char* str, bool isInput)
+{
+    LOGV("TaskProcess::parseParams will parse %s", str);
+    android::String8 paramStr(str);
+    UniquePtr<std::vector<android::String8> > paramTokens(StringUtil::split(paramStr, ','));
+    if (paramTokens.get() == NULL) {
+        LOGE("split failed");
+        return false;
+    }
+    std::vector<android::String8>& tokens = *(paramTokens.get());
+    for (size_t i = 0; i < tokens.size(); i++) {
+        UniquePtr<std::vector<android::String8> > itemTokens(StringUtil::split(tokens[i], ':'));
+        if (itemTokens.get() == NULL) {
+            LOGE("split failed");
+            return false;
+        }
+        if (itemTokens->size() != 2) {
+            LOGE("size mismatch %d", itemTokens->size());
+            return false;
+        }
+        std::vector<android::String8>& item = *(itemTokens.get());
+        if (StringUtil::compare(item[0], "id") == 0) {
+            Param param(EId, item[1]);
+            list.push_back(param);
+            LOGD(" id %s", param.getParamString().string());
+        } else if (StringUtil::compare(item[0], "val") == 0) {
+            Param param(EVal, item[1]);
+            list.push_back(param);
+            LOGD(" val %s", param.getParamString().string());
+        } else if (isInput && (StringUtil::compare(item[0], "consti") == 0)) {
+            long long value = atoll(item[1].string());
+            TaskCase::Value v(value);
+            Param param(v);
+            list.push_back(param);
+            LOGD("consti %lld", value);
+        } else if (isInput && (StringUtil::compare(item[0], "constf") == 0)) {
+            double value = atof(item[1].string());
+            TaskCase::Value v(value);
+            Param param(v);
+            list.push_back(param);
+            LOGD("constf %f", value);
+        } else {
+            LOGE("unrecognized word %s", item[0].string());
+            return false;
+        }
+        LOGV("TaskProcess::parseParams %d-th type %d", i, list[i].getType());
+    }
+   return true;
+}
+
+bool TaskProcess::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    if (StringUtil::compare(name, "method") == 0) {
+        UniquePtr<std::vector<android::String8> > tokenPtr(StringUtil::split(value, ':'));
+        std::vector<android::String8>* tokens = tokenPtr.get();
+        if (tokens == NULL) {
+            LOGE("split failed");
+            return false;
+        }
+        if (tokens->size() != 2) {
+            LOGE("cannot parse attr %s %s", name.string(), value.string());
+            return false;
+        }
+        if (StringUtil::compare(tokens->at(0), "builtin") == 0) {
+            mType = EBuiltin;
+        } else if (StringUtil::compare(tokens->at(0), "script") == 0) {
+            mType = EScript;
+        } else {
+            LOGE("cannot parse attr %s %s", name.string(), value.string());
+            return false;
+        }
+        mName.append(tokens->at(1));
+        return true;
+    } else if (StringUtil::compare(name, "input") == 0) {
+        return parseParams(mInput, value, true);
+    } else if (StringUtil::compare(name, "output") == 0) {
+        return parseParams(mOutput, value, false);
+    } else {
+        LOGE("cannot parse attr %s %s", name.string(), value.string());
+        return false;
+    }
+}
+
+TaskProcess::Param::Param(TaskProcess::ParamType type, android::String8& string)
+    : mType(type),
+      mString(string)
+{
+    ASSERT((type == TaskProcess::EId) || (type == TaskProcess::EVal));
+
+}
+
+TaskProcess::Param::Param(TaskCase::Value& val)
+    : mType(TaskProcess::EConst),
+      mValue(val)
+{
+
+}
+
+TaskProcess::ParamType TaskProcess::Param::getType()
+{
+    return mType;
+}
+
+android::String8& TaskProcess::Param::getParamString()
+{
+    ASSERT((mType == TaskProcess::EId) || (mType == TaskProcess::EVal));
+    return mString;
+}
+
+TaskCase::Value& TaskProcess::Param::getValue()
+{
+    ASSERT(mType == TaskProcess::EConst);
+    return mValue;
+}
+
+TaskCase::Value* TaskProcess::Param::getValuePtr()
+{
+    ASSERT(mType == TaskProcess::EConst);
+    return &mValue;
+}
diff --git a/suite/audio_quality/lib/src/task/TaskSave.cpp b/suite/audio_quality/lib/src/task/TaskSave.cpp
new file mode 100644
index 0000000..d62b846
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskSave.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include <UniquePtr.h>
+
+#include "Log.h"
+#include "FileUtil.h"
+#include "Report.h"
+#include "StringUtil.h"
+#include "task/TaskCase.h"
+#include "task/TaskGeneric.h"
+#include "task/TaskSave.h"
+
+static const android::String8 STR_FILE("file");
+static const android::String8 STR_REPORT("report");
+
+TaskSave::TaskSave()
+    : TaskGeneric(TaskGeneric::ETaskSave)
+{
+    const android::String8* list[] = {&STR_FILE, &STR_REPORT, NULL};
+    registerSupportedStringAttributes(list);
+}
+
+TaskSave::~TaskSave()
+{
+
+}
+
+bool TaskSave::handleFile()
+{
+    android::String8 fileValue;
+    if (!findStringAttribute(STR_FILE, fileValue)) {
+        LOGI("no saving to file");
+        return true; // true as there is no need to save
+    }
+
+    UniquePtr<std::vector<android::String8> > list(StringUtil::split(fileValue, ','));
+    std::vector<android::String8>* listp = list.get();
+    if (listp == NULL) {
+        LOGE("alloc failed");
+        return false;
+    }
+
+    android::String8 dirName;
+    if (!FileUtil::prepare(dirName)) {
+        LOGE("cannot prepare report dir");
+        return false;
+    }
+    android::String8 caseName;
+    if (!getTestCase()->getCaseName(caseName)) {
+        return false;
+    }
+    dirName.appendPath(caseName);
+    int result = mkdir(dirName.string(), S_IRWXU);
+    if ((result == -1) && (errno != EEXIST)) {
+        LOGE("mkdir of save dir %s failed, error %d", dirName.string(), errno);
+        return false;
+    }
+
+    for (size_t i = 0; i < listp->size(); i++) {
+        UniquePtr<std::list<TaskCase::BufferPair> > buffers(
+                getTestCase()->findAllBuffers((*listp)[i]));
+        std::list<TaskCase::BufferPair>* buffersp = buffers.get();
+        if (buffersp == NULL) {
+            LOGE("no buffer for given pattern %s", ((*listp)[i]).string());
+            return false;
+        }
+        std::list<TaskCase::BufferPair>::iterator it = buffersp->begin();
+        std::list<TaskCase::BufferPair>::iterator end = buffersp->end();
+        for (; it != end; it++) {
+            android::String8 fileName(dirName);
+            fileName.appendPath(it->first);
+            if (!it->second->saveToFile(fileName)) {
+                LOGE("save failed");
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool TaskSave::handleReport()
+{
+    android::String8 reportValue;
+    if (!findStringAttribute(STR_REPORT, reportValue)) {
+        LOGI("no saving to report");
+        return true; // true as there is no need to save
+    }
+
+    UniquePtr<std::vector<android::String8> > list(StringUtil::split(reportValue, ','));
+    std::vector<android::String8>* listp = list.get();
+    if (listp == NULL) {
+        LOGE("alloc failed");
+        return false;
+    }
+    Report::Instance()->printf("=== Values stored ===");
+    for (size_t i = 0; i < listp->size(); i++) {
+        UniquePtr<std::list<TaskCase::ValuePair> > values(
+                getTestCase()->findAllValues((*listp)[i]));
+        std::list<TaskCase::ValuePair>* valuesp = values.get();
+        if (valuesp == NULL) {
+            LOGE("no value for given pattern %s", ((*listp)[i]).string());
+            return false;
+        }
+        std::list<TaskCase::ValuePair>::iterator it = values->begin();
+        std::list<TaskCase::ValuePair>::iterator end = values->end();
+        for (; it != end; it++) {
+            if (it->second.getType() == TaskCase::Value::ETypeDouble) {
+                Report::Instance()->printf("   %s: %f", it->first.string(),
+                        it->second.getDouble());
+            } else { //64bit int
+                Report::Instance()->printf("   %s: %lld", it->first.string(),
+                        it->second.getInt64());
+            }
+        }
+    }
+    return true;
+}
+
+TaskGeneric::ExecutionResult TaskSave::run()
+{
+    bool failed = false;
+    if (!handleFile()) {
+        failed = true;
+    }
+    if (!handleReport()) {
+        failed = true;
+    }
+    if (failed) {
+        return TaskGeneric::EResultError;
+    } else {
+        return TaskGeneric::EResultOK;
+    }
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskSequential.cpp b/suite/audio_quality/lib/src/task/TaskSequential.cpp
new file mode 100644
index 0000000..8c60cb7f
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskSequential.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+#include <stdlib.h>
+#include <algorithm>
+#include "Log.h"
+#include "StringUtil.h"
+#include "task/TaskSequential.h"
+#include "task/TaskCase.h"
+#include "task/TaskAsync.h"
+
+TaskSequential::TaskSequential()
+    : TaskGeneric(TaskGeneric::ETaskSequential),
+      mRepeatCount(1),
+      mRepeatIndex(-1)
+{
+
+}
+
+TaskSequential::~TaskSequential()
+{
+
+}
+
+
+TaskGeneric::ExecutionResult TaskSequential::run()
+{
+    mRepeatIndex = -1;
+    bool storeIndex = (mIndexName.length() == 0 ? false: true);
+    if (storeIndex && !getTestCase()->registerIndex(mIndexName, mRepeatIndex)) {
+        if (!getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
+            LOGE("register/update of index %s failed", mIndexName.string());
+            return TaskGeneric::EResultError;
+        }
+    }
+
+    TaskGeneric::ExecutionResult firstError(TaskGeneric::EResultOK);
+
+    for (mRepeatIndex = 0; mRepeatIndex < mRepeatCount; mRepeatIndex++) {
+        LOGI("  TaskSequential index %s loop %d-th", mIndexName.string(), mRepeatIndex);
+        if (storeIndex && !getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
+            return TaskGeneric::EResultError;
+        }
+        std::list<TaskGeneric*>::iterator i = getChildren().begin();
+        std::list<TaskGeneric*>::iterator end = getChildren().end();
+        for (; i != end; i++) {
+            TaskGeneric* child = *i;
+            TaskGeneric::ExecutionResult result = child->run();
+            if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
+                firstError = result;
+                break;
+            }
+        }
+        TaskGeneric::ExecutionResult result = runAsyncTasksQueued();
+        if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
+                    firstError = result;
+        }
+        switch (firstError) {
+        case TaskGeneric::EResultOK:
+        case TaskGeneric::EResultContinue:
+            // continue at the last index should be treated as OK
+            firstError = TaskGeneric::EResultOK;
+            break; // continue for loop
+        case TaskGeneric:: EResultBreakOneLoop:
+            return TaskGeneric::EResultOK;
+        case TaskGeneric::EResultError:
+        case TaskGeneric::EResultFail:
+        case TaskGeneric::EResultPass:
+            mRepeatIndex = mRepeatCount; //exit for loop
+            break;
+        }
+    }
+    // update to the loop exit value
+    if (storeIndex && !getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
+        return TaskGeneric::EResultError;
+    }
+    return firstError;
+}
+
+bool TaskSequential::queueAsyncTask(TaskAsync* task)
+{
+    std::list<TaskAsync*>::iterator it;
+    it = std::find(mAsyncTasks.begin(), mAsyncTasks.end(), task);
+    if (it != mAsyncTasks.end()) { // already queued
+        return true;
+    }
+    mAsyncTasks.push_back(task);
+    return true;
+}
+
+TaskGeneric::ExecutionResult TaskSequential::runAsyncTasksQueued()
+{
+    std::list<TaskAsync*>::iterator i = mAsyncTasks.begin();
+    std::list<TaskAsync*>::iterator end = mAsyncTasks.end();
+    TaskGeneric::ExecutionResult firstError(TaskGeneric::EResultOK);
+
+    for (; i != end; i++) {
+        TaskAsync* child = *i;
+        TaskGeneric::ExecutionResult result = child->complete();
+        if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
+            firstError = result;
+        }
+    }
+    mAsyncTasks.clear();
+    return firstError;
+}
+
+
+bool TaskSequential::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    if (StringUtil::compare(name, "repeat") == 0) {
+        mRepeatCount = atoi(value.string());
+        if (mRepeatCount <= 0) {
+            LOGE("TaskSequential::parseAttribute invalid value %s for key %s",
+                    value.string(), name.string());
+            return false;
+        }
+        return true;
+    } else if (StringUtil::compare(name, "index") == 0) {
+        mIndexName.append(value);
+        LOGD("TaskSequential::parseAttribute index %s", mIndexName.string());
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/suite/audio_quality/lib/src/task/TaskSound.cpp b/suite/audio_quality/lib/src/task/TaskSound.cpp
new file mode 100644
index 0000000..f530203
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskSound.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include <UniquePtr.h>
+#include "Log.h"
+#include "audio/AudioSignalFactory.h"
+#include "audio/RemoteAudio.h"
+#include "StringUtil.h"
+#include "task/TaskCase.h"
+#include "task/TaskSound.h"
+
+static const android::String8 STR_ID("id");
+static const android::String8 STR_TYPE("type");
+
+TaskSound::TaskSound()
+    : TaskGeneric(TaskGeneric::ETaskSound),
+      mPreload(false)
+{
+    const android::String8* list[] = {&STR_ID, &STR_TYPE, NULL};
+    registerSupportedStringAttributes(list);
+}
+
+TaskSound::~TaskSound()
+{
+
+}
+
+bool TaskSound::parseAttribute(const android::String8& name, const android::String8& value)
+{
+    if (StringUtil::compare(name, "preload") == 0) {
+            if (StringUtil::compare(value, "1") == 0) {
+                mPreload = true;
+            }
+            return true;
+    }
+    return TaskGeneric::parseAttribute(name, value);
+}
+
+TaskGeneric::ExecutionResult TaskSound::run()
+{
+    //TODO : needs to support data generated from process
+    android::String8 id;
+    if (!findStringAttribute(STR_ID, id)) {
+        LOGE("TaskSound::run %s string not found", STR_ID.string());
+        return TaskGeneric::EResultError;
+    }
+    android::String8 type;
+    if (!findStringAttribute(STR_TYPE, type)) {
+        LOGE("TaskSound::run %s string not found", STR_TYPE.string());
+        return TaskGeneric::EResultError;
+    }
+    UniquePtr<std::vector<android::String8> > tokens(StringUtil::split(type, ':'));
+    if (tokens.get() == NULL) {
+        LOGE("alloc failed");
+        return TaskGeneric::EResultError;
+    }
+    android::sp<Buffer> buffer;
+    if (StringUtil::compare(tokens->at(0), "file") == 0) {
+        if (tokens->size() != 2) {
+            LOGE("Wrong number of parameters %d", tokens->size());
+        }
+        buffer = Buffer::loadFromFile(tokens->at(1));
+    } else if (StringUtil::compare(tokens->at(0), "sin") == 0) {
+        if (tokens->size() != 4) {
+            LOGE("Wrong number of parameters %d", tokens->size());
+        }
+        int amplitude = atoi(tokens->at(1).string());
+        int freq = atoi(tokens->at(2).string());
+        int time = atoi(tokens->at(3).string());
+        int samples = time * AudioHardware::ESampleRate_44100 / 1000;
+        buffer = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS, amplitude,
+                AudioHardware::ESampleRate_44100, freq, samples, true);
+    } else if (StringUtil::compare(tokens->at(0), "random") == 0) {
+        if (tokens->size() != 3) {
+            LOGE("Wrong number of parameters %d", tokens->size());
+        }
+        int amplitude = atoi(tokens->at(1).string());
+        int time = atoi(tokens->at(2).string());
+        int samples = time * AudioHardware::ESampleRate_44100 / 1000;
+        buffer = AudioSignalFactory::generateWhiteNoise(AudioHardware::E2BPS, amplitude,
+                samples, true);
+    } else { // unknown word
+        LOGE("TaskSound::run unknown word in type %s", type.string());
+        // next buffer check will return
+    }
+
+    if (buffer.get() == NULL) {
+        return TaskGeneric::EResultError;
+    }
+    if (!getTestCase()->registerBuffer(id, buffer)) {
+        LOGE("TaskSound::run registering buffer %s failed", id.string());
+        return TaskGeneric::EResultError;
+    }
+    if (mPreload) {
+        int downloadId;
+        if (!getTestCase()->getRemoteAudio()->downloadData(id, buffer, downloadId)) {
+            return TaskGeneric::EResultError;
+        }
+        LOGI("Downloaded buffer %s to DUT with id %d", id.string(), downloadId);
+    }
+    return TaskGeneric::EResultOK;
+}
+
+
+