Merge "Camera3: fix ZSL processor3 issues"
diff --git a/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp
new file mode 100644
index 0000000..01f8d65
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <openssl/aes.h>
+
+#include "AesCtrDecryptor.h"
+
+namespace clearkeydrm {
+
+static const size_t kBlockBitCount = kBlockSize * 8;
+
+android::status_t AesCtrDecryptor::decrypt(const android::Vector<uint8_t>& key,
+        const Iv iv, const uint8_t* source,
+        uint8_t* destination,
+        const SubSample* subSamples,
+        size_t numSubSamples,
+        size_t* bytesDecryptedOut) {
+    uint32_t blockOffset = 0;
+    uint8_t previousEncryptedCounter[kBlockSize];
+    memset(previousEncryptedCounter, 0, kBlockSize);
+
+    size_t offset = 0;
+    AES_KEY opensslKey;
+    AES_set_encrypt_key(key.array(), kBlockBitCount, &opensslKey);
+    Iv opensslIv;
+    memcpy(opensslIv, iv, sizeof(opensslIv));
+
+    for (size_t i = 0; i < numSubSamples; ++i) {
+        const SubSample& subSample = subSamples[i];
+
+        if (subSample.mNumBytesOfClearData > 0) {
+            memcpy(destination + offset, source + offset,
+                    subSample.mNumBytesOfClearData);
+            offset += subSample.mNumBytesOfClearData;
+        }
+
+        if (subSample.mNumBytesOfEncryptedData > 0) {
+            AES_ctr128_encrypt(source + offset, destination + offset,
+                    subSample.mNumBytesOfEncryptedData, &opensslKey,
+                    opensslIv, previousEncryptedCounter,
+                    &blockOffset);
+            offset += subSample.mNumBytesOfEncryptedData;
+        }
+    }
+
+    *bytesDecryptedOut = offset;
+    return android::OK;
+}
+
+} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h
new file mode 100644
index 0000000..b416266
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/AesCtrDecryptor.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_AES_CTR_DECRYPTOR_H_
+#define CLEARKEY_AES_CTR_DECRYPTOR_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <Utils.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+
+#include "ClearKeyTypes.h"
+
+namespace clearkeydrm {
+
+class AesCtrDecryptor {
+public:
+    AesCtrDecryptor() {}
+
+    android::status_t decrypt(const android::Vector<uint8_t>& key, const Iv iv,
+            const uint8_t* source, uint8_t* destination,
+            const SubSample* subSamples, size_t numSubSamples,
+            size_t* bytesDecryptedOut);
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(AesCtrDecryptor);
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_AES_CTR_DECRYPTOR_H_
diff --git a/drm/mediadrm/plugins/clearkey/Android.mk b/drm/mediadrm/plugins/clearkey/Android.mk
new file mode 100644
index 0000000..22a85b4
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/Android.mk
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2014 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.
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    AesCtrDecryptor.cpp \
+    ClearKeyUUID.cpp \
+    CreatePluginFactories.cpp \
+    CryptoFactory.cpp \
+    CryptoPlugin.cpp \
+    DrmFactory.cpp \
+    DrmPlugin.cpp \
+    InitDataParser.cpp \
+    JsonWebKey.cpp \
+    Session.cpp \
+    SessionLibrary.cpp \
+    Utils.cpp \
+
+LOCAL_C_INCLUDES := \
+    bionic \
+    external/jsmn \
+    external/openssl/include \
+    frameworks/av/drm/mediadrm/plugins/clearkey \
+    frameworks/av/include \
+    frameworks/native/include \
+
+LOCAL_MODULE := libdrmclearkeyplugin
+
+LOCAL_PROPRIETARY_MODULE := true
+LOCAL_MODULE_RELATIVE_PATH := mediadrm
+
+LOCAL_SHARED_LIBRARIES := \
+    libcrypto \
+    liblog \
+    libstagefright_foundation \
+    libutils \
+
+LOCAL_STATIC_LIBRARIES := \
+    libjsmn \
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+#########################################################################
+# Build unit tests
+
+include $(LOCAL_PATH)/tests/Android.mk
diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyTypes.h b/drm/mediadrm/plugins/clearkey/ClearKeyTypes.h
new file mode 100644
index 0000000..a28959a
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/ClearKeyTypes.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_TYPES_H_
+#define CLEARKEY_TYPES_H_
+
+#include <media/hardware/CryptoAPI.h>
+#include <openssl/aes.h>
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+
+namespace clearkeydrm {
+
+const uint8_t kBlockSize = AES_BLOCK_SIZE;
+typedef uint8_t KeyId[kBlockSize];
+typedef uint8_t Iv[kBlockSize];
+
+typedef android::CryptoPlugin::SubSample SubSample;
+
+typedef android::KeyedVector<android::Vector<uint8_t>,
+        android::Vector<uint8_t> > KeyMap;
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_TYPES_H_
diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp
new file mode 100644
index 0000000..ed050f7
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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 <string.h>
+
+#include "ClearKeyUUID.h"
+
+namespace clearkeydrm {
+
+bool isClearKeyUUID(const uint8_t uuid[16]) {
+    static const uint8_t kClearKeyUUID[16] = {
+        0x10,0x77,0xEF,0xEC,0xC0,0xB2,0x4D,0x02,
+        0xAC,0xE3,0x3C,0x1E,0x52,0xE2,0xFB,0x4B
+    };
+
+    return !memcmp(uuid, kClearKeyUUID, sizeof(kClearKeyUUID));
+}
+
+} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/ClearKeyUUID.h b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.h
new file mode 100644
index 0000000..ac99418
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/ClearKeyUUID.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_UUID_H_
+#define CLEARKEY_UUID_H_
+
+#include <stdint.h>
+
+namespace clearkeydrm {
+
+bool isClearKeyUUID(const uint8_t uuid[16]);
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_UUID_H_
diff --git a/drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp
new file mode 100644
index 0000000..ec1420e
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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 "CreatePluginFactories.h"
+
+#include "CryptoFactory.h"
+#include "DrmFactory.h"
+
+extern "C" {
+
+android::DrmFactory* createDrmFactory() {
+    return new clearkeydrm::DrmFactory();
+}
+
+android::CryptoFactory* createCryptoFactory() {
+    return new clearkeydrm::CryptoFactory();
+}
+
+} // extern "C"
diff --git a/drm/mediadrm/plugins/clearkey/CreatePluginFactories.h b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.h
new file mode 100644
index 0000000..d9acec1
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/CreatePluginFactories.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_CREATE_PLUGIN_FACTORIES_H_
+#define CLEARKEY_CREATE_PLUGIN_FACTORIES_H_
+
+#include <media/drm/DrmAPI.h>
+#include <media/hardware/CryptoAPI.h>
+
+extern "C" {
+    android::DrmFactory* createDrmFactory();
+    android::CryptoFactory* createCryptoFactory();
+}
+
+#endif // CLEARKEY_CREATE_PLUGIN_FACTORIES_H_
diff --git a/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp b/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp
new file mode 100644
index 0000000..ee3189b
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/CryptoFactory.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+
+#include "CryptoFactory.h"
+
+#include "ClearKeyUUID.h"
+#include "CryptoPlugin.h"
+#include "Session.h"
+#include "SessionLibrary.h"
+
+namespace clearkeydrm {
+
+bool CryptoFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) const {
+    return isClearKeyUUID(uuid);
+}
+
+android::status_t CryptoFactory::createPlugin(
+        const uint8_t uuid[16],
+        const void* data, size_t size,
+        android::CryptoPlugin** plugin) {
+    if (!isCryptoSchemeSupported(uuid)) {
+        *plugin = NULL;
+        return android::BAD_VALUE;
+    }
+
+    android::sp<Session> session = SessionLibrary::get()->findSession(
+            data, size);
+    *plugin = new CryptoPlugin(session);
+    return android::OK;
+}
+
+} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/CryptoFactory.h b/drm/mediadrm/plugins/clearkey/CryptoFactory.h
new file mode 100644
index 0000000..568bc4b
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/CryptoFactory.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_CRYPTO_FACTORY_H_
+#define CLEARKEY_CRYPTO_FACTORY_H_
+
+#include <media/hardware/CryptoAPI.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+
+namespace clearkeydrm {
+
+class CryptoFactory : public android::CryptoFactory {
+public:
+    CryptoFactory() {}
+    virtual ~CryptoFactory() {}
+
+    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) const;
+
+    virtual android::status_t createPlugin(
+            const uint8_t uuid[16],
+            const void* data, size_t size,
+            android::CryptoPlugin** plugin);
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(CryptoFactory);
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_CRYPTO_FACTORY_H_
diff --git a/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp
new file mode 100644
index 0000000..adad136
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <media/stagefright/MediaErrors.h>
+#include <utils/Errors.h>
+
+#include "CryptoPlugin.h"
+
+namespace clearkeydrm {
+
+using android::Vector;
+using android::AString;
+using android::status_t;
+
+// Returns negative values for error code and positive values for the size of
+// decrypted data.  In theory, the output size can be larger than the input
+// size, but in practice this will never happen for AES-CTR.
+ssize_t CryptoPlugin::decrypt(bool secure, const KeyId keyId, const Iv iv,
+                              Mode mode, const void* srcPtr,
+                              const SubSample* subSamples, size_t numSubSamples,
+                              void* dstPtr, AString* errorDetailMsg) {
+    if (secure) {
+        errorDetailMsg->setTo("Secure decryption is not supported with "
+                              "ClearKey.");
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    if (mode == kMode_Unencrypted) {
+        size_t offset = 0;
+        for (size_t i = 0; i < numSubSamples; ++i) {
+            const SubSample& subSample = subSamples[i];
+
+            if (subSample.mNumBytesOfEncryptedData != 0) {
+                errorDetailMsg->setTo(
+                        "Encrypted subsamples found in allegedly unencrypted "
+                        "data.");
+                return android::ERROR_DRM_DECRYPT;
+            }
+
+            if (subSample.mNumBytesOfClearData != 0) {
+                memcpy(reinterpret_cast<uint8_t*>(dstPtr) + offset,
+                       reinterpret_cast<const uint8_t*>(srcPtr) + offset,
+                       subSample.mNumBytesOfClearData);
+                offset += subSample.mNumBytesOfClearData;
+            }
+        }
+        return static_cast<ssize_t>(offset);
+    } else if (mode == kMode_AES_CTR) {
+        size_t bytesDecrypted;
+        status_t res = mSession->decrypt(keyId, iv, srcPtr, dstPtr, subSamples,
+                                         numSubSamples, &bytesDecrypted);
+        if (res == android::OK) {
+            return static_cast<ssize_t>(bytesDecrypted);
+        } else {
+            errorDetailMsg->setTo("Decryption Error");
+            return static_cast<ssize_t>(res);
+        }
+    } else {
+        errorDetailMsg->setTo(
+                "Selected encryption mode is not supported by the ClearKey DRM "
+                "Plugin.");
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+}
+
+}  // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/CryptoPlugin.h b/drm/mediadrm/plugins/clearkey/CryptoPlugin.h
new file mode 100644
index 0000000..002d9e0
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/CryptoPlugin.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_CRYPTO_PLUGIN_H_
+#define CLEARKEY_CRYPTO_PLUGIN_H_
+
+#include <media/hardware/CryptoAPI.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+
+#include "ClearKeyTypes.h"
+#include "Session.h"
+#include "Utils.h"
+
+namespace clearkeydrm {
+
+class CryptoPlugin : public android::CryptoPlugin {
+public:
+    CryptoPlugin(const android::sp<Session>& session) : mSession(session) {}
+    virtual ~CryptoPlugin() {}
+
+    virtual bool requiresSecureDecoderComponent(const char* mime) const {
+        UNUSED(mime);
+        return false;
+    }
+
+    virtual ssize_t decrypt(
+            bool secure, const KeyId keyId, const Iv iv,
+            Mode mode, const void* srcPtr,
+            const SubSample* subSamples, size_t numSubSamples,
+            void* dstPtr, android::AString* errorDetailMsg);
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(CryptoPlugin);
+
+    android::sp<Session> mSession;
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_CRYPTO_PLUGIN_H_
diff --git a/drm/mediadrm/plugins/clearkey/DrmFactory.cpp b/drm/mediadrm/plugins/clearkey/DrmFactory.cpp
new file mode 100644
index 0000000..40275cf
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/DrmFactory.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <utils/Errors.h>
+
+#include "DrmFactory.h"
+
+#include "DrmPlugin.h"
+#include "ClearKeyUUID.h"
+#include "SessionLibrary.h"
+
+namespace clearkeydrm {
+
+bool DrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) {
+    return isClearKeyUUID(uuid);
+}
+
+bool DrmFactory::isContentTypeSupported(const android::String8 &initDataType) {
+    // This should match the types handed by InitDataParser.
+    return initDataType == "cenc" ||
+           initDataType == "webm";
+}
+
+android::status_t DrmFactory::createDrmPlugin(
+        const uint8_t uuid[16], android::DrmPlugin** plugin) {
+    if (!isCryptoSchemeSupported(uuid)) {
+        *plugin = NULL;
+        return android::BAD_VALUE;
+    }
+
+    *plugin = new DrmPlugin(SessionLibrary::get());
+    return android::OK;
+}
+
+} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/DrmFactory.h b/drm/mediadrm/plugins/clearkey/DrmFactory.h
new file mode 100644
index 0000000..164d3d0
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/DrmFactory.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_DRM_FACTORY_H_
+#define CLEARKEY_DRM_FACTORY_H_
+
+#include <media/drm/DrmAPI.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+
+#include "Utils.h"
+
+namespace clearkeydrm {
+
+class DrmFactory : public android::DrmFactory {
+public:
+    DrmFactory() {}
+    virtual ~DrmFactory() {}
+
+    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]);
+
+    virtual bool isContentTypeSupported(const android::String8 &initDataType);
+
+    virtual android::status_t createDrmPlugin(
+            const uint8_t uuid[16], android::DrmPlugin** plugin);
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(DrmFactory);
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_DRM_FACTORY_H_
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
new file mode 100644
index 0000000..96fca94
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <media/stagefright/MediaErrors.h>
+#include <utils/StrongPointer.h>
+
+#include "DrmPlugin.h"
+
+#include "Session.h"
+
+namespace clearkeydrm {
+
+using android::sp;
+
+status_t DrmPlugin::openSession(Vector<uint8_t>& sessionId) {
+    sp<Session> session = mSessionLibrary->createSession();
+    sessionId = session->sessionId();
+    return android::OK;
+}
+
+status_t DrmPlugin::closeSession(const Vector<uint8_t>& sessionId) {
+    sp<Session> session = mSessionLibrary->findSession(sessionId);
+    mSessionLibrary->destroySession(session);
+    return android::OK;
+}
+
+status_t DrmPlugin::getKeyRequest(
+        const Vector<uint8_t>& scope,
+        const Vector<uint8_t>& initData,
+        const String8& initDataType,
+        KeyType keyType,
+        const KeyedVector<String8, String8>& optionalParameters,
+        Vector<uint8_t>& request,
+        String8& defaultUrl) {
+    UNUSED(optionalParameters);
+    if (keyType != kKeyType_Streaming) {
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    sp<Session> session = mSessionLibrary->findSession(scope);
+    defaultUrl.clear();
+    return session->getKeyRequest(initData, initDataType, &request);
+}
+
+status_t DrmPlugin::provideKeyResponse(
+        const Vector<uint8_t>& scope,
+        const Vector<uint8_t>& response,
+        Vector<uint8_t>& keySetId) {
+    sp<Session> session = mSessionLibrary->findSession(scope);
+    status_t res = session->provideKeyResponse(response);
+    if (res == android::OK) {
+        keySetId.clear();
+    }
+    return res;
+}
+
+status_t DrmPlugin::getPropertyString(
+        const String8& name, String8& value) const {
+    if (name == "vendor") {
+        value = "Google";
+    } else if (name == "version") {
+        value = "1.0";
+    } else if (name == "description") {
+        value = "ClearKey CDM";
+    } else if (name == "algorithms") {
+        value = "";
+    } else {
+        ALOGE("App requested unknown string property %s", name.string());
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+    return android::OK;
+}
+
+}  // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
new file mode 100644
index 0000000..bfbc6bf
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_DRM_PLUGIN_H_
+#define CLEARKEY_DRM_PLUGIN_H_
+
+#include <media/drm/DrmAPI.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaErrors.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/List.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "SessionLibrary.h"
+#include "Utils.h"
+
+namespace clearkeydrm {
+
+using android::KeyedVector;
+using android::List;
+using android::status_t;
+using android::String8;
+using android::Vector;
+
+class DrmPlugin : public android::DrmPlugin {
+public:
+    DrmPlugin(SessionLibrary* sessionLibrary)
+            : mSessionLibrary(sessionLibrary) {}
+    virtual ~DrmPlugin() {}
+
+    virtual status_t openSession(Vector<uint8_t>& sessionId);
+
+    virtual status_t closeSession(const Vector<uint8_t>& sessionId);
+
+    virtual status_t getKeyRequest(
+            const Vector<uint8_t>& scope,
+            const Vector<uint8_t>& initData,
+            const String8& initDataType,
+            KeyType keyType,
+            const KeyedVector<String8, String8>& optionalParameters,
+            Vector<uint8_t>& request,
+            String8& defaultUrl);
+
+    virtual status_t provideKeyResponse(
+            const Vector<uint8_t>& scope,
+            const Vector<uint8_t>& response,
+            Vector<uint8_t>& keySetId);
+
+    virtual status_t removeKeys(const Vector<uint8_t>& sessionId) {
+        UNUSED(sessionId);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t restoreKeys(
+            const Vector<uint8_t>& sessionId,
+            const Vector<uint8_t>& keySetId) {
+        UNUSED(sessionId);
+        UNUSED(keySetId);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t queryKeyStatus(
+            const Vector<uint8_t>& sessionId,
+            KeyedVector<String8, String8>& infoMap) const {
+        UNUSED(sessionId);
+        UNUSED(infoMap);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t getProvisionRequest(
+            const String8& cert_type,
+            const String8& cert_authority,
+            Vector<uint8_t>& request,
+            String8& defaultUrl) {
+        UNUSED(cert_type);
+        UNUSED(cert_authority);
+        UNUSED(request);
+        UNUSED(defaultUrl);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t provideProvisionResponse(
+            const Vector<uint8_t>& response,
+            Vector<uint8_t>& certificate,
+            Vector<uint8_t>& wrappedKey) {
+        UNUSED(response);
+        UNUSED(certificate);
+        UNUSED(wrappedKey);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t getSecureStops(List<Vector<uint8_t> >& secureStops) {
+        UNUSED(secureStops);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t releaseSecureStops(const Vector<uint8_t>& ssRelease) {
+        UNUSED(ssRelease);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t getPropertyString(
+            const String8& name, String8& value) const;
+
+    virtual status_t getPropertyByteArray(
+            const String8& name, Vector<uint8_t>& value) const {
+        UNUSED(name);
+        UNUSED(value);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t setPropertyString(
+            const String8& name, const String8& value) {
+        UNUSED(name);
+        UNUSED(value);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t setPropertyByteArray(
+            const String8& name, const Vector<uint8_t>& value) {
+        UNUSED(name);
+        UNUSED(value);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t setCipherAlgorithm(
+            const Vector<uint8_t>& sessionId, const String8& algorithm) {
+        UNUSED(sessionId);
+        UNUSED(algorithm);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t setMacAlgorithm(
+            const Vector<uint8_t>& sessionId, const String8& algorithm) {
+        UNUSED(sessionId);
+        UNUSED(algorithm);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t encrypt(
+            const Vector<uint8_t>& sessionId,
+            const Vector<uint8_t>& keyId,
+            const Vector<uint8_t>& input,
+            const Vector<uint8_t>& iv,
+            Vector<uint8_t>& output) {
+        UNUSED(sessionId);
+        UNUSED(keyId);
+        UNUSED(input);
+        UNUSED(iv);
+        UNUSED(output);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t decrypt(
+            const Vector<uint8_t>& sessionId,
+            const Vector<uint8_t>& keyId,
+            const Vector<uint8_t>& input,
+            const Vector<uint8_t>& iv,
+            Vector<uint8_t>& output) {
+        UNUSED(sessionId);
+        UNUSED(keyId);
+        UNUSED(input);
+        UNUSED(iv);
+        UNUSED(output);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t sign(
+            const Vector<uint8_t>& sessionId,
+            const Vector<uint8_t>& keyId,
+            const Vector<uint8_t>& message,
+            Vector<uint8_t>& signature) {
+        UNUSED(sessionId);
+        UNUSED(keyId);
+        UNUSED(message);
+        UNUSED(signature);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t verify(
+            const Vector<uint8_t>& sessionId,
+            const Vector<uint8_t>& keyId,
+            const Vector<uint8_t>& message,
+            const Vector<uint8_t>& signature, bool& match) {
+        UNUSED(sessionId);
+        UNUSED(keyId);
+        UNUSED(message);
+        UNUSED(signature);
+        UNUSED(match);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    virtual status_t signRSA(
+            const Vector<uint8_t>& sessionId,
+            const String8& algorithm,
+            const Vector<uint8_t>& message,
+            const Vector<uint8_t>& wrappedKey,
+            Vector<uint8_t>& signature) {
+        UNUSED(sessionId);
+        UNUSED(algorithm);
+        UNUSED(message);
+        UNUSED(wrappedKey);
+        UNUSED(signature);
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(DrmPlugin);
+
+    SessionLibrary* mSessionLibrary;
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_DRM_PLUGIN_H_
diff --git a/drm/mediadrm/plugins/clearkey/InitDataParser.cpp b/drm/mediadrm/plugins/clearkey/InitDataParser.cpp
new file mode 100644
index 0000000..c22d73a
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/InitDataParser.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <endian.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <media/stagefright/MediaErrors.h>
+#include <string.h>
+
+#include "InitDataParser.h"
+
+#include "ClearKeyUUID.h"
+#include "Utils.h"
+
+namespace clearkeydrm {
+
+using android::AString;
+using android::String8;
+using android::Vector;
+
+namespace {
+    const size_t kKeyIdSize = 16;
+    const size_t kSystemIdSize = 16;
+}
+
+android::status_t InitDataParser::parse(const Vector<uint8_t>& initData,
+        const String8& initDataType,
+        Vector<uint8_t>* licenseRequest) {
+    // Build a list of the key IDs
+    Vector<const uint8_t*> keyIds;
+    if (initDataType == "cenc") {
+        android::status_t res = parsePssh(initData, &keyIds);
+        if (res != android::OK) {
+            return res;
+        }
+    } else if (initDataType == "webm") {
+        // WebM "init data" is just a single key ID
+        if (initData.size() != kKeyIdSize) {
+            return android::ERROR_DRM_CANNOT_HANDLE;
+        }
+        keyIds.push(initData.array());
+    } else {
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    // Build the request
+    String8 requestJson = generateRequest(keyIds);
+    licenseRequest->clear();
+    licenseRequest->appendArray(
+            reinterpret_cast<const uint8_t*>(requestJson.string()),
+            requestJson.size());
+    return android::OK;
+}
+
+android::status_t InitDataParser::parsePssh(const Vector<uint8_t>& initData,
+        Vector<const uint8_t*>* keyIds) {
+    size_t readPosition = 0;
+
+    // Validate size field
+    uint32_t expectedSize = initData.size();
+    expectedSize = htonl(expectedSize);
+    if (memcmp(&initData[readPosition], &expectedSize,
+               sizeof(expectedSize)) != 0) {
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+    readPosition += sizeof(expectedSize);
+
+    // Validate PSSH box identifier
+    const char psshIdentifier[4] = {'p', 's', 's', 'h'};
+    if (memcmp(&initData[readPosition], psshIdentifier,
+               sizeof(psshIdentifier)) != 0) {
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+    readPosition += sizeof(psshIdentifier);
+
+    // Validate EME version number
+    const uint8_t psshVersion1[4] = {1, 0, 0, 0};
+    if (memcmp(&initData[readPosition], psshVersion1,
+               sizeof(psshVersion1)) != 0) {
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+    readPosition += sizeof(psshVersion1);
+
+    // Validate system ID
+    if (!isClearKeyUUID(&initData[readPosition])) {
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+    readPosition += kSystemIdSize;
+
+    // Read key ID count
+    uint32_t keyIdCount;
+    memcpy(&keyIdCount, &initData[readPosition], sizeof(keyIdCount));
+    keyIdCount = ntohl(keyIdCount);
+    readPosition += sizeof(keyIdCount);
+    if (readPosition + (keyIdCount * kKeyIdSize) !=
+            initData.size() - sizeof(uint32_t)) {
+        return android::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    // Calculate the key ID offsets
+    for (uint32_t i = 0; i < keyIdCount; ++i) {
+        size_t keyIdPosition = readPosition + (i * kKeyIdSize);
+        keyIds->push(&initData[keyIdPosition]);
+    }
+    return android::OK;
+}
+
+String8 InitDataParser::generateRequest(const Vector<const uint8_t*>& keyIds) {
+    const String8 kRequestPrefix("{\"kids\":[");
+    const String8 kRequestSuffix("],\"type\":\"temporary\"}");
+    const String8 kBase64Padding("=");
+
+    String8 request(kRequestPrefix);
+    AString encodedId;
+    for (size_t i = 0; i < keyIds.size(); ++i) {
+        encodedId.clear();
+        android::encodeBase64(keyIds[i], kKeyIdSize, &encodedId);
+        if (i != 0) {
+            request.append(",");
+        }
+        request.appendFormat("\"%s\"", encodedId.c_str());
+    }
+    request.append(kRequestSuffix);
+
+    // Android's Base64 encoder produces padding. EME forbids padding.
+    request.removeAll(kBase64Padding);
+    return request;
+}
+
+} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/InitDataParser.h b/drm/mediadrm/plugins/clearkey/InitDataParser.h
new file mode 100644
index 0000000..9505d2a
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/InitDataParser.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_INIT_DATA_PARSER_H_
+#define CLEARKEY_INIT_DATA_PARSER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace clearkeydrm {
+
+class InitDataParser {
+public:
+    InitDataParser() {}
+
+    android::status_t parse(const android::Vector<uint8_t>& initData,
+            const android::String8& initDataType,
+            android::Vector<uint8_t>* licenseRequest);
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(InitDataParser);
+
+    android::status_t parsePssh(const android::Vector<uint8_t>& initData,
+            android::Vector<const uint8_t*>* keyIds);
+
+    android::String8 generateRequest(
+            const android::Vector<const uint8_t*>& keyIds);
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_INIT_DATA_PARSER_H_
diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp
new file mode 100644
index 0000000..53ffae4
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/JsonWebKey.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "JsonWebKey"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <utils/Log.h>
+
+#include "JsonWebKey.h"
+
+namespace {
+const android::String8 kKeysTag("keys");
+const android::String8 kKeyTypeTag("kty");
+const android::String8 kSymmetricKeyValue("oct");
+const android::String8 kKeyTag("k");
+const android::String8 kKeyIdTag("kid");
+const android::String8 kBase64Padding("=");
+}
+
+namespace clearkeydrm {
+
+using android::ABuffer;
+using android::AString;
+
+JsonWebKey::JsonWebKey() {
+}
+
+JsonWebKey::~JsonWebKey() {
+}
+
+/*
+ * Parses a JSON Web Key Set string, initializes a KeyMap with key id:key
+ * pairs from the JSON Web Key Set. Both key ids and keys are base64url
+ * encoded. The KeyMap contains base64url decoded key id:key pairs.
+ *
+ * @return Returns false for errors, true for success.
+ */
+bool JsonWebKey::extractKeysFromJsonWebKeySet(const String8& jsonWebKeySet,
+        KeyMap* keys) {
+
+    keys->clear();
+    if (!parseJsonWebKeySet(jsonWebKeySet, &mJsonObjects)) {
+        return false;
+    }
+
+    // mJsonObjects[0] contains the entire JSON Web Key Set, including
+    // all the base64 encoded keys. Each key is also stored separately as
+    // a JSON object in mJsonObjects[1..n] where n is the total
+    // number of keys in the set.
+    if (!isJsonWebKeySet(mJsonObjects[0])) {
+        return false;
+    }
+
+    String8 encodedKey, encodedKeyId;
+    Vector<uint8_t> decodedKey, decodedKeyId;
+
+    // mJsonObjects[1] contains the first JSON Web Key in the set
+    for (size_t i = 1; i < mJsonObjects.size(); ++i) {
+        encodedKeyId.clear();
+        encodedKey.clear();
+
+        if (!parseJsonObject(mJsonObjects[i], &mTokens))
+            return false;
+
+        if (findKey(mJsonObjects[i], &encodedKeyId, &encodedKey)) {
+            if (encodedKeyId.isEmpty() || encodedKey.isEmpty()) {
+                ALOGE("Must have both key id and key in the JsonWebKey set.");
+                continue;
+            }
+
+            if (!decodeBase64String(encodedKeyId, &decodedKeyId)) {
+                ALOGE("Failed to decode key id(%s)", encodedKeyId.string());
+                continue;
+            }
+
+            if (!decodeBase64String(encodedKey, &decodedKey)) {
+                ALOGE("Failed to decode key(%s)", encodedKey.string());
+                continue;
+            }
+
+            keys->add(decodedKeyId, decodedKey);
+        }
+    }
+    return true;
+}
+
+bool JsonWebKey::decodeBase64String(const String8& encodedText,
+        Vector<uint8_t>* decodedText) {
+
+    decodedText->clear();
+
+    // encodedText should not contain padding characters as per EME spec.
+    if (encodedText.find(kBase64Padding) != -1) {
+        return false;
+    }
+
+    // Since android::decodeBase64() requires padding characters,
+    // add them so length of encodedText is exactly a multiple of 4.
+    int remainder = encodedText.length() % 4;
+    String8 paddedText(encodedText);
+    if (remainder > 0) {
+        for (int i = 0; i < 4 - remainder; ++i) {
+            paddedText.append(kBase64Padding);
+        }
+    }
+
+    android::sp<ABuffer> buffer =
+            android::decodeBase64(AString(paddedText.string()));
+    if (buffer == NULL) {
+        ALOGE("Malformed base64 encoded content found.");
+        return false;
+    }
+
+    decodedText->appendArray(buffer->base(), buffer->size());
+    return true;
+}
+
+bool JsonWebKey::findKey(const String8& jsonObject, String8* keyId,
+        String8* encodedKey) {
+
+    String8 key, value;
+
+    // Only allow symmetric key, i.e. "kty":"oct" pair.
+    if (jsonObject.find(kKeyTypeTag) >= 0) {
+        findValue(kKeyTypeTag, &value);
+        if (0 != value.compare(kSymmetricKeyValue))
+            return false;
+    }
+
+    if (jsonObject.find(kKeyIdTag) >= 0) {
+        findValue(kKeyIdTag, keyId);
+    }
+
+    if (jsonObject.find(kKeyTag) >= 0) {
+        findValue(kKeyTag, encodedKey);
+    }
+    return true;
+}
+
+void JsonWebKey::findValue(const String8 &key, String8* value) {
+    value->clear();
+    const char* valueToken;
+    for (Vector<String8>::const_iterator nextToken = mTokens.begin();
+        nextToken != mTokens.end(); ++nextToken) {
+        if (0 == (*nextToken).compare(key)) {
+            if (nextToken + 1 == mTokens.end())
+                break;
+            valueToken = (*(nextToken + 1)).string();
+            value->setTo(valueToken);
+            nextToken++;
+            break;
+        }
+    }
+}
+
+bool JsonWebKey::isJsonWebKeySet(const String8& jsonObject) const {
+    if (jsonObject.find(kKeysTag) == -1) {
+        ALOGE("JSON Web Key does not contain keys.");
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Parses a JSON objects string and initializes a vector of tokens.
+ *
+ * @return Returns false for errors, true for success.
+ */
+bool JsonWebKey::parseJsonObject(const String8& jsonObject,
+        Vector<String8>* tokens) {
+    jsmn_parser parser;
+
+    jsmn_init(&parser);
+    int numTokens = jsmn_parse(&parser,
+        jsonObject.string(), jsonObject.size(), NULL, 0);
+    if (numTokens < 0) {
+        ALOGE("Parser returns error code=%d", numTokens);
+        return false;
+    }
+
+    unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
+    mJsmnTokens.clear();
+    mJsmnTokens.setCapacity(jsmnTokensSize);
+
+    jsmn_init(&parser);
+    int status = jsmn_parse(&parser, jsonObject.string(),
+        jsonObject.size(), mJsmnTokens.editArray(), numTokens);
+    if (status < 0) {
+        ALOGE("Parser returns error code=%d", status);
+        return false;
+    }
+
+    tokens->clear();
+    String8 token;
+    const char *pjs;
+    for (int j = 0; j < numTokens; ++j) {
+        pjs = jsonObject.string() + mJsmnTokens[j].start;
+        if (mJsmnTokens[j].type == JSMN_STRING ||
+                mJsmnTokens[j].type == JSMN_PRIMITIVE) {
+            token.setTo(pjs, mJsmnTokens[j].end - mJsmnTokens[j].start);
+            tokens->add(token);
+        }
+    }
+    return true;
+}
+
+/*
+ * Parses JSON Web Key Set string and initializes a vector of JSON objects.
+ *
+ * @return Returns false for errors, true for success.
+ */
+bool JsonWebKey::parseJsonWebKeySet(const String8& jsonWebKeySet,
+        Vector<String8>* jsonObjects) {
+    if (jsonWebKeySet.isEmpty()) {
+        ALOGE("Empty JSON Web Key");
+        return false;
+    }
+
+    // The jsmn parser only supports unicode encoding.
+    jsmn_parser parser;
+
+    // Computes number of tokens. A token marks the type, offset in
+    // the original string.
+    jsmn_init(&parser);
+    int numTokens = jsmn_parse(&parser,
+            jsonWebKeySet.string(), jsonWebKeySet.size(), NULL, 0);
+    if (numTokens < 0) {
+        ALOGE("Parser returns error code=%d", numTokens);
+        return false;
+    }
+
+    unsigned int jsmnTokensSize = numTokens * sizeof(jsmntok_t);
+    mJsmnTokens.setCapacity(jsmnTokensSize);
+
+    jsmn_init(&parser);
+    int status = jsmn_parse(&parser, jsonWebKeySet.string(),
+            jsonWebKeySet.size(), mJsmnTokens.editArray(), numTokens);
+    if (status < 0) {
+        ALOGE("Parser returns error code=%d", status);
+        return false;
+    }
+
+    String8 token;
+    const char *pjs;
+    for (int i = 0; i < numTokens; ++i) {
+        pjs = jsonWebKeySet.string() + mJsmnTokens[i].start;
+        if (mJsmnTokens[i].type == JSMN_OBJECT) {
+            token.setTo(pjs, mJsmnTokens[i].end - mJsmnTokens[i].start);
+            jsonObjects->add(token);
+        }
+    }
+    return true;
+}
+
+}  // clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/JsonWebKey.h b/drm/mediadrm/plugins/clearkey/JsonWebKey.h
new file mode 100644
index 0000000..6ae50ee
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/JsonWebKey.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_JSON_WEB_KEY_H_
+#define CLEARKEY_JSON_WEB_KEY_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+
+#include "jsmn.h"
+#include "Utils.h"
+#include "ClearKeyTypes.h"
+
+namespace clearkeydrm {
+
+using android::KeyedVector;
+using android::sp;
+using android::String8;
+using android::Vector;
+
+class JsonWebKey {
+ public:
+    JsonWebKey();
+    virtual ~JsonWebKey();
+
+    bool extractKeysFromJsonWebKeySet(const String8& jsonWebKeySet,
+            KeyMap* keys);
+
+ private:
+    Vector<jsmntok_t> mJsmnTokens;
+    Vector<String8> mJsonObjects;
+    Vector<String8> mTokens;
+
+    bool decodeBase64String(const String8& encodedText,
+            Vector<uint8_t>* decodedText);
+    bool findKey(const String8& jsonObject, String8* keyId,
+            String8* encodedKey);
+    void findValue(const String8 &key, String8* value);
+    bool isJsonWebKeySet(const String8& jsonObject) const;
+    bool parseJsonObject(const String8& jsonObject, Vector<String8>* tokens);
+    bool parseJsonWebKeySet(const String8& jsonWebKeySet, Vector<String8>* jsonObjects);
+
+    DISALLOW_EVIL_CONSTRUCTORS(JsonWebKey);
+};
+
+}  // namespace clearkeydrm
+
+#endif  // CLEARKEY_JSON_WEB_KEY_H_
diff --git a/drm/mediadrm/plugins/clearkey/Session.cpp b/drm/mediadrm/plugins/clearkey/Session.cpp
new file mode 100644
index 0000000..95016f5
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/Session.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <media/stagefright/MediaErrors.h>
+#include <utils/String8.h>
+
+#include "Session.h"
+
+#include "AesCtrDecryptor.h"
+#include "InitDataParser.h"
+#include "JsonWebKey.h"
+
+namespace clearkeydrm {
+
+using android::Mutex;
+using android::String8;
+using android::Vector;
+using android::status_t;
+
+status_t Session::getKeyRequest(
+        const Vector<uint8_t>& initData,
+        const String8& initDataType,
+        Vector<uint8_t>* keyRequest) const {
+    InitDataParser parser;
+    return parser.parse(initData, initDataType, keyRequest);
+}
+
+status_t Session::provideKeyResponse(const Vector<uint8_t>& response) {
+    String8 responseString(
+            reinterpret_cast<const char*>(response.array()), response.size());
+    KeyMap keys;
+
+    Mutex::Autolock lock(mMapLock);
+    JsonWebKey parser;
+    if (parser.extractKeysFromJsonWebKeySet(responseString, &keys)) {
+        for (size_t i = 0; i < keys.size(); ++i) {
+            const KeyMap::key_type& keyId = keys.keyAt(i);
+            const KeyMap::value_type& key = keys.valueAt(i);
+            mKeyMap.add(keyId, key);
+        }
+        return android::OK;
+    } else {
+        return android::ERROR_DRM_UNKNOWN;
+    }
+}
+
+status_t Session::decrypt(
+        const KeyId keyId, const Iv iv, const void* source,
+        void* destination, const SubSample* subSamples,
+        size_t numSubSamples, size_t* bytesDecryptedOut) {
+    Mutex::Autolock lock(mMapLock);
+
+    Vector<uint8_t> keyIdVector;
+    keyIdVector.appendArray(keyId, kBlockSize);
+    if (mKeyMap.indexOfKey(keyIdVector) < 0) {
+        return android::ERROR_DRM_NO_LICENSE;
+    }
+
+    const Vector<uint8_t>& key = mKeyMap.valueFor(keyIdVector);
+    AesCtrDecryptor decryptor;
+    return decryptor.decrypt(
+            key, iv,
+            reinterpret_cast<const uint8_t*>(source),
+            reinterpret_cast<uint8_t*>(destination), subSamples,
+            numSubSamples, bytesDecryptedOut);
+}
+
+} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/Session.h b/drm/mediadrm/plugins/clearkey/Session.h
new file mode 100644
index 0000000..cab0dc3
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/Session.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_SESSION_H_
+#define CLEARKEY_SESSION_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "ClearKeyTypes.h"
+#include "Utils.h"
+
+namespace clearkeydrm {
+
+class Session : public android::RefBase {
+public:
+    explicit Session(const android::Vector<uint8_t>& sessionId)
+            : mSessionId(sessionId) {}
+    virtual ~Session() {}
+
+    const android::Vector<uint8_t>& sessionId() const { return mSessionId; }
+
+    android::status_t getKeyRequest(
+            const android::Vector<uint8_t>& initData,
+            const android::String8& initDataType,
+            android::Vector<uint8_t>* keyRequest) const;
+
+    android::status_t provideKeyResponse(
+            const android::Vector<uint8_t>& response);
+
+    android::status_t decrypt(
+            const KeyId keyId, const Iv iv, const void* source,
+            void* destination, const SubSample* subSamples,
+            size_t numSubSamples, size_t* bytesDecryptedOut);
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(Session);
+
+    const android::Vector<uint8_t> mSessionId;
+
+    android::Mutex mMapLock;
+    KeyMap mKeyMap;
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_SESSION_H_
diff --git a/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp b/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp
new file mode 100644
index 0000000..d047c53
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/SessionLibrary.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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 "ClearKeyCryptoPlugin"
+#include <utils/Log.h>
+
+#include <utils/String8.h>
+
+#include "SessionLibrary.h"
+
+namespace clearkeydrm {
+
+using android::Mutex;
+using android::sp;
+using android::String8;
+using android::Vector;
+
+Mutex SessionLibrary::sSingletonLock;
+SessionLibrary* SessionLibrary::sSingleton = NULL;
+
+SessionLibrary* SessionLibrary::get() {
+    Mutex::Autolock lock(sSingletonLock);
+
+    if (sSingleton == NULL) {
+        ALOGD("Instantiating Session Library Singleton.");
+        sSingleton = new SessionLibrary();
+    }
+
+    return sSingleton;
+}
+
+const sp<Session>& SessionLibrary::createSession() {
+    Mutex::Autolock lock(mSessionsLock);
+
+    String8 sessionIdString = String8::format("%u", mNextSessionId);
+    mNextSessionId += 1;
+    Vector<uint8_t> sessionId;
+    sessionId.appendArray(
+            reinterpret_cast<const uint8_t*>(sessionIdString.string()),
+            sessionIdString.size());
+
+    mSessions.add(sessionId, new Session(sessionId));
+    return mSessions.valueFor(sessionId);
+}
+
+const sp<Session>& SessionLibrary::findSession(
+        const Vector<uint8_t>& sessionId) {
+    Mutex::Autolock lock(mSessionsLock);
+    return mSessions.valueFor(sessionId);
+}
+
+const sp<Session>& SessionLibrary::findSession(
+        const void* data, size_t size) {
+    Vector<uint8_t> sessionId;
+    sessionId.appendArray(reinterpret_cast<const uint8_t*>(data), size);
+    return findSession(sessionId);
+}
+
+void SessionLibrary::destroySession(const sp<Session>& session) {
+    Mutex::Autolock lock(mSessionsLock);\
+    mSessions.removeItem(session->sessionId());
+}
+
+} // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/SessionLibrary.h b/drm/mediadrm/plugins/clearkey/SessionLibrary.h
new file mode 100644
index 0000000..56c8828
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/SessionLibrary.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_SESSION_LIBRARY_H_
+#define CLEARKEY_SESSION_LIBRARY_H_
+
+#include <utils/KeyedVector.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+#include "Session.h"
+#include "Utils.h"
+
+namespace clearkeydrm {
+
+class SessionLibrary {
+public:
+    static SessionLibrary* get();
+
+    const android::sp<Session>& createSession();
+
+    const android::sp<Session>& findSession(
+            const android::Vector<uint8_t>& sessionId);
+
+    const android::sp<Session>& findSession(const void* data, size_t size);
+
+    void destroySession(const android::sp<Session>& session);
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(SessionLibrary);
+
+    SessionLibrary() : mNextSessionId(1) {}
+
+    static android::Mutex sSingletonLock;
+    static SessionLibrary* sSingleton;
+
+    android::Mutex mSessionsLock;
+    uint32_t mNextSessionId;
+    android::KeyedVector<android::Vector<uint8_t>, android::sp<Session> >
+            mSessions;
+};
+
+} // namespace clearkeydrm
+
+#endif // CLEARKEY_SESSION_LIBRARY_H_
diff --git a/drm/mediadrm/plugins/clearkey/Utils.cpp b/drm/mediadrm/plugins/clearkey/Utils.cpp
new file mode 100644
index 0000000..93c643b
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/Utils.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Utils.h"
+
+namespace android {
+
+bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &rhs) {
+    if (lhs.size() < rhs.size()) {
+        return true;
+    } else if (lhs.size() > rhs.size()) {
+        return false;
+    }
+    return memcmp((void *)lhs.array(), (void *)rhs.array(), rhs.size()) < 0;
+}
+
+} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/Utils.h b/drm/mediadrm/plugins/clearkey/Utils.h
new file mode 100644
index 0000000..2543124
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/Utils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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 CLEARKEY_UTILS_H_
+#define CLEARKEY_UTILS_H_
+
+#include <utils/Vector.h>
+
+// Add a comparison operator for this Vector specialization so that it can be
+// used as a key in a KeyedVector.
+namespace android {
+
+bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &rhs);
+
+} // namespace android
+
+#define UNUSED(x) (void)(x);
+
+#endif // CLEARKEY_UTILS_H_
diff --git a/drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp
new file mode 100644
index 0000000..039e402
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/tests/AesCtrDecryptorUnittest.cpp
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2014 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 <gtest/gtest.h>
+#include <string.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "AesCtrDecryptor.h"
+
+namespace clearkeydrm {
+
+using namespace android;
+
+class AesCtrDecryptorTest : public ::testing::Test {
+  protected:
+    typedef uint8_t Key[kBlockSize];
+
+    status_t attemptDecrypt(const Key& key, const Iv& iv, const uint8_t* source,
+                            uint8_t* destination, const SubSample* subSamples,
+                            size_t numSubSamples, size_t* bytesDecryptedOut) {
+        Vector<uint8_t> keyVector;
+        keyVector.appendArray(key, kBlockSize);
+
+        AesCtrDecryptor decryptor;
+        return decryptor.decrypt(keyVector, iv, source, destination, subSamples,
+                                 numSubSamples, bytesDecryptedOut);
+    }
+
+    template <size_t totalSize>
+    void attemptDecryptExpectingSuccess(const Key& key, const Iv& iv,
+                                        const uint8_t* encrypted,
+                                        const uint8_t* decrypted,
+                                        const SubSample* subSamples,
+                                        size_t numSubSamples) {
+        uint8_t outputBuffer[totalSize] = {};
+        size_t bytesDecrypted = 0;
+        ASSERT_EQ(android::OK, attemptDecrypt(key, iv, encrypted, outputBuffer,
+                                              subSamples, numSubSamples,
+                                              &bytesDecrypted));
+        EXPECT_EQ(totalSize, bytesDecrypted);
+        EXPECT_EQ(0, memcmp(outputBuffer, decrypted, totalSize));
+    }
+};
+
+TEST_F(AesCtrDecryptorTest, DecryptsContiguousEncryptedBlock) {
+    const size_t kTotalSize = 64;
+    const size_t kNumSubsamples = 1;
+
+    // Test vectors from NIST-800-38A
+    Key key = {
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+    };
+
+    Iv iv = {
+        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+    };
+
+    uint8_t encrypted[kTotalSize] = {
+        0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+        0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
+        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
+        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
+        0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e,
+        0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
+        0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1,
+        0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee
+    };
+
+    uint8_t decrypted[kTotalSize] = {
+        0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+        0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+        0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+        0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
+        0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+        0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+        0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+        0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+    };
+
+    SubSample subSamples[kNumSubsamples] = {
+        {0, 64}
+    };
+
+    attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted,
+                                               subSamples, kNumSubsamples);
+}
+
+TEST_F(AesCtrDecryptorTest, DecryptsAlignedBifurcatedEncryptedBlock) {
+    const size_t kTotalSize = 64;
+    const size_t kNumSubsamples = 2;
+
+    // Test vectors from NIST-800-38A
+    Key key = {
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+    };
+
+    Iv iv = {
+        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+    };
+
+    uint8_t encrypted[kTotalSize] = {
+        0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+        0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
+        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
+        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
+        0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e,
+        0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
+        0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1,
+        0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee
+    };
+
+    uint8_t decrypted[kTotalSize] = {
+        0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+        0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+        0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+        0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
+        0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+        0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+        0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+        0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+    };
+
+    SubSample subSamples[kNumSubsamples] = {
+        {0, 32},
+        {0, 32}
+    };
+
+    attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted,
+                                               subSamples, kNumSubsamples);
+}
+
+TEST_F(AesCtrDecryptorTest, DecryptsUnalignedBifurcatedEncryptedBlock) {
+    const size_t kTotalSize = 64;
+    const size_t kNumSubsamples = 2;
+
+    // Test vectors from NIST-800-38A
+    Key key = {
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+    };
+
+    Iv iv = {
+        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+    };
+
+    uint8_t encrypted[kTotalSize] = {
+        0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+        0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
+        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
+        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
+        0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e,
+        0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
+        0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1,
+        0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee
+    };
+
+    uint8_t decrypted[kTotalSize] = {
+        0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+        0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+        0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+        0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
+        0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+        0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+        0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+        0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+    };
+
+    SubSample subSamples[kNumSubsamples] = {
+        {0, 29},
+        {0, 35}
+    };
+
+    attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted,
+                                               subSamples, kNumSubsamples);
+}
+
+TEST_F(AesCtrDecryptorTest, DecryptsOneMixedSubSample) {
+    const size_t kTotalSize = 72;
+    const size_t kNumSubsamples = 1;
+
+    // Based on test vectors from NIST-800-38A
+    Key key = {
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+    };
+
+    Iv iv = {
+        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+    };
+
+    uint8_t encrypted[kTotalSize] = {
+        // 8 clear bytes
+        0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb,
+        // 64 encrypted bytes
+        0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+        0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
+        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
+        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
+        0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e,
+        0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
+        0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1,
+        0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee
+    };
+
+    uint8_t decrypted[kTotalSize] = {
+        0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb,
+        0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+        0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+        0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+        0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
+        0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+        0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+        0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+        0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+    };
+
+    SubSample subSamples[kNumSubsamples] = {
+        {8, 64}
+    };
+
+    attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted,
+                                               subSamples, kNumSubsamples);
+}
+
+TEST_F(AesCtrDecryptorTest, DecryptsAlignedMixedSubSamples) {
+    const size_t kTotalSize = 80;
+    const size_t kNumSubsamples = 2;
+
+    // Based on test vectors from NIST-800-38A
+    Key key = {
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+    };
+
+    Iv iv = {
+        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+    };
+
+    uint8_t encrypted[kTotalSize] = {
+        // 8 clear bytes
+        0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb,
+        // 32 encrypted bytes
+        0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+        0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
+        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
+        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
+        // 8 clear bytes
+        0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55,
+        // 32 encrypted bytes
+        0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e,
+        0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
+        0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1,
+        0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee
+    };
+
+    uint8_t decrypted[kTotalSize] = {
+        0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb,
+        0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+        0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+        0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+        0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
+        0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55,
+        0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+        0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+        0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+        0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+    };
+
+    SubSample subSamples[kNumSubsamples] = {
+        {8, 32},
+        {8, 32}
+    };
+
+    attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted,
+                                               subSamples, kNumSubsamples);
+}
+
+TEST_F(AesCtrDecryptorTest, DecryptsUnalignedMixedSubSamples) {
+    const size_t kTotalSize = 80;
+    const size_t kNumSubsamples = 2;
+
+    // Based on test vectors from NIST-800-38A
+    Key key = {
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+    };
+
+    Iv iv = {
+        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+    };
+
+    uint8_t encrypted[kTotalSize] = {
+        // 8 clear bytes
+        0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb,
+        // 30 encrypted bytes
+        0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+        0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
+        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
+        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff,
+        // 8 clear bytes
+        0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55,
+        // 34 encrypted bytes
+        0xfd, 0xff, 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5,
+        0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0,
+        0x3e, 0xab, 0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe,
+        0x03, 0xd1, 0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00,
+        0x9c, 0xee
+    };
+
+    uint8_t decrypted[kTotalSize] = {
+        0xf0, 0x13, 0xca, 0xc7, 0x00, 0x64, 0x0b, 0xbb,
+        0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+        0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+        0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
+        0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x94, 0xba,
+        0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55, 0x8e, 0x51,
+        0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
+        0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
+        0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
+        0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
+    };
+
+    SubSample subSamples[kNumSubsamples] = {
+        {8, 30},
+        {8, 34}
+    };
+
+    attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted,
+                                               subSamples, kNumSubsamples);
+}
+
+TEST_F(AesCtrDecryptorTest, DecryptsComplexMixedSubSamples) {
+    const size_t kTotalSize = 72;
+    const size_t kNumSubsamples = 6;
+
+    // Based on test vectors from NIST-800-38A
+    Key key = {
+        0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+    };
+
+    Iv iv = {
+        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+    };
+
+    uint8_t encrypted[kTotalSize] = {
+        // 4 clear bytes
+        0xf0, 0x13, 0xca, 0xc7,
+        // 1 encrypted bytes
+        0x87,
+        // 9 encrypted bytes
+        0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26, 0x1b,
+        0xef,
+        // 11 clear bytes
+        0x81, 0x4f, 0x24, 0x87, 0x0e, 0xde, 0xba, 0xad,
+        0x11, 0x9b, 0x46,
+        // 20 encrypted bytes
+        0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
+        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
+        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff,
+        // 8 clear bytes
+        0x94, 0xba, 0x88, 0x2e, 0x0e, 0x12, 0x11, 0x55,
+        // 3 clear bytes
+        0x10, 0xf5, 0x22,
+        // 14 encrypted bytes
+        0xfd, 0xff, 0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5,
+        0xd3, 0x5e, 0x5b, 0x4f, 0x09, 0x02,
+        // 2 clear bytes
+        0x02, 0x01
+    };
+
+    uint8_t decrypted[kTotalSize] = {
+        0xf0, 0x13, 0xca, 0xc7, 0x6b, 0xc1, 0xbe, 0xe2,
+        0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x81, 0x4f,
+        0x24, 0x87, 0x0e, 0xde, 0xba, 0xad, 0x11, 0x9b,
+        0x46, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae,
+        0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e,
+        0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x94, 0xba, 0x88,
+        0x2e, 0x0e, 0x12, 0x11, 0x55, 0x10, 0xf5, 0x22,
+        0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c,
+        0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x02, 0x01
+    };
+
+    SubSample subSamples[kNumSubsamples] = {
+        {4, 1},
+        {0, 9},
+        {11, 20},
+        {8, 0},
+        {3, 14},
+        {2, 0}
+    };
+
+    attemptDecryptExpectingSuccess<kTotalSize>(key, iv, encrypted, decrypted,
+                                               subSamples, kNumSubsamples);
+}
+
+}  // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/tests/Android.mk b/drm/mediadrm/plugins/clearkey/tests/Android.mk
new file mode 100644
index 0000000..ac5bb21
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/tests/Android.mk
@@ -0,0 +1,52 @@
+#
+# Copyright (C) 2014 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.
+#
+# ----------------------------------------------------------------
+# Builds ClearKey Drm Tests
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ClearKeyDrmUnitTest
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    AesCtrDecryptorUnittest.cpp \
+    InitDataParserUnittest.cpp \
+    JsonWebKeyUnittest.cpp \
+
+LOCAL_C_INCLUDES := \
+    bionic \
+    external/gtest/include \
+    external/jsmn \
+    external/openssl/include \
+    external/stlport/stlport \
+    frameworks/av/drm/mediadrm/plugins/clearkey \
+    frameworks/av/include \
+    frameworks/native/include \
+
+LOCAL_STATIC_LIBRARIES := \
+    libgtest \
+    libgtest_main \
+
+LOCAL_SHARED_LIBRARIES := \
+    libcrypto \
+    libdrmclearkeyplugin \
+    liblog \
+    libstagefright_foundation \
+    libstlport \
+    libutils \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp
new file mode 100644
index 0000000..4ba65ed
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/tests/InitDataParserUnittest.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2014 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 <gtest/gtest.h>
+#include <string.h>
+
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/foundation/base64.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "InitDataParser.h"
+
+namespace clearkeydrm {
+
+using namespace android;
+
+namespace {
+    const size_t kKeyIdSize = 16;
+    const String8 kCencType("cenc");
+    const String8 kWebMType("webm");
+    const String8 kBase64Padding("=");
+}
+
+class InitDataParserTest : public ::testing::Test {
+  protected:
+    status_t attemptParse(const Vector<uint8_t>& initData,
+                          const String8& initDataType,
+                          Vector<uint8_t>* licenseRequest) {
+        InitDataParser parser;
+        return parser.parse(initData, initDataType, licenseRequest);
+    }
+
+    void attemptParseExpectingSuccess(const Vector<uint8_t>& initData,
+                                      const String8& initDataType,
+                                      const Vector<String8>& expectedKeys) {
+        const String8 kRequestPrefix("{\"kids\":[");
+        const String8 kRequestSuffix("],\"type\":\"temporary\"}");
+        Vector<uint8_t> request;
+        ASSERT_EQ(android::OK, attemptParse(initData, initDataType, &request));
+
+        String8 requestString(reinterpret_cast<const char*>(request.array()),
+                              request.size());
+        EXPECT_EQ(0, requestString.find(kRequestPrefix));
+        EXPECT_EQ(requestString.size() - kRequestSuffix.size(),
+                  requestString.find(kRequestSuffix));
+        for (size_t i = 0; i < expectedKeys.size(); ++i) {
+            AString encodedIdAString;
+            android::encodeBase64(expectedKeys[i], kKeyIdSize,
+                                  &encodedIdAString);
+            String8 encodedId(encodedIdAString.c_str());
+            encodedId.removeAll(kBase64Padding);
+            EXPECT_TRUE(requestString.contains(encodedId));
+        }
+    }
+
+    void attemptParseExpectingFailure(const Vector<uint8_t>& initData,
+                                      const String8& initDataType) {
+        Vector<uint8_t> request;
+        ASSERT_NE(android::OK, attemptParse(initData, initDataType, &request));
+        EXPECT_EQ(0, request.size());
+    }
+};
+
+TEST_F(InitDataParserTest, ParsesSingleKeyPssh) {
+    uint8_t pssh[52] = {
+        0, 0, 0, 52,                                    // Total Size
+        'p', 's', 's', 'h',                             // PSSH
+        1, 0, 0, 0,                                     // Version
+        0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID
+        0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+        0, 0, 0, 1,                                     // Key Count
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1
+        0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, //   "01234567890ABCDE"
+        0, 0, 0, 0                                      // Data Size (always 0)
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(pssh, 52);
+
+    Vector<String8> expectedKeys;
+    expectedKeys.push(String8("01234567890ABCDE"));
+
+    attemptParseExpectingSuccess(initData, kCencType, expectedKeys);
+}
+
+TEST_F(InitDataParserTest, ParsesMultipleKeyPssh) {
+    uint8_t pssh[84] = {
+        0, 0, 0, 84,                                    // Total Size
+        'p', 's', 's', 'h',                             // PSSH
+        1, 0, 0, 0,                                     // Version
+        0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID
+        0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+        0, 0, 0, 3,                                     // Key Count
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1
+        0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, //   "01234567890ABCDE"
+        0x43, 0x6c, 0x65, 0x61, 0x72, 0x4b, 0x65, 0x79, // Key ID #2
+        0x43, 0x6c, 0x65, 0x61, 0x72, 0x4b, 0x65, 0x79, //   "ClearKeyClearKey"
+        0x20, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x20, // Key ID #3
+        0x20, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x20, //   " GOOGLE  GOOGLE "
+        0, 0, 0, 0                                      // Data Size (always 0)
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(pssh, 84);
+
+    Vector<String8> expectedKeys;
+    expectedKeys.push(String8("01234567890ABCDE"));
+    expectedKeys.push(String8("ClearKeyClearKey"));
+    expectedKeys.push(String8(" GOOGLE  GOOGLE "));
+
+    attemptParseExpectingSuccess(initData, kCencType, expectedKeys);
+}
+
+TEST_F(InitDataParserTest, ParsesWebM) {
+    uint8_t initDataRaw[16] = {
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID
+        0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, //   "01234567890ABCDE"
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(initDataRaw, 16);
+
+    Vector<String8> expectedKeys;
+    expectedKeys.push(String8("01234567890ABCDE"));
+
+    attemptParseExpectingSuccess(initData, kWebMType, expectedKeys);
+}
+
+TEST_F(InitDataParserTest, FailsForPsshTooSmall) {
+    uint8_t pssh[16] = {
+        0, 0, 0, 52,
+        'p', 's', 's', 'h',
+        1, 0, 0, 0,
+        0x10, 0x77, 0xef, 0xec
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(pssh, 16);
+
+    attemptParseExpectingFailure(initData, kCencType);
+}
+
+TEST_F(InitDataParserTest, FailsForWebMTooSmall) {
+    uint8_t initDataRaw[8] = {
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(initDataRaw, 8);
+
+    attemptParseExpectingFailure(initData, kWebMType);
+}
+
+TEST_F(InitDataParserTest, FailsForPsshBadSystemId) {
+    uint8_t pssh[52] = {
+        0, 0, 0, 52,                                    // Total Size
+        'p', 's', 's', 'h',                             // PSSH
+        1, 0, 0, 0,                                     // Version
+        0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, // System ID
+        0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+        0, 0, 0, 1,                                     // Key Count
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1
+        0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, //   "01234567890ABCDE"
+        0, 0, 0, 0                                      // Data Size (always 0)
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(pssh, 52);
+
+    attemptParseExpectingFailure(initData, kCencType);
+}
+
+TEST_F(InitDataParserTest, FailsForPsshBadSize) {
+    uint8_t pssh[52] = {
+        0, 0, 70, 200,                                  // Total Size
+        'p', 's', 's', 'h',                             // PSSH
+        1, 0, 0, 0,                                     // Version
+        0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID
+        0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+        0, 0, 0, 1,                                     // Key Count
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1
+        0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, //   "01234567890ABCDE"
+        0, 0, 0, 0                                      // Data Size (always 0)
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(pssh, 52);
+
+    attemptParseExpectingFailure(initData, kCencType);
+}
+
+TEST_F(InitDataParserTest, FailsForPsshWrongVersion) {
+    uint8_t pssh[52] = {
+        0, 0, 0, 52,                                    // Total Size
+        'p', 's', 's', 'h',                             // PSSH
+        0, 0, 0, 0,                                     // Version
+        0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID
+        0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+        0, 0, 0, 1,                                     // Key Count
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1
+        0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, //   "01234567890ABCDE"
+        0, 0, 0, 0                                      // Data Size (always 0)
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(pssh, 52);
+
+    attemptParseExpectingFailure(initData, kCencType);
+}
+
+TEST_F(InitDataParserTest, FailsForPsshBadKeyCount) {
+    uint8_t pssh[52] = {
+        0, 0, 0, 52,                                    // Total Size
+        'p', 's', 's', 'h',                             // PSSH
+        1, 0, 0, 0,                                     // Version
+        0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // System ID
+        0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+        0, 0, 0, 7,                                     // Key Count
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // Key ID #1
+        0x38, 0x39, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, //   "01234567890ABCDE"
+        0, 0, 0, 0                                      // Data Size (always 0)
+    };
+    Vector<uint8_t> initData;
+    initData.appendArray(pssh, 52);
+
+    attemptParseExpectingFailure(initData, kCencType);
+}
+
+}  // namespace clearkeydrm
diff --git a/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp b/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp
new file mode 100644
index 0000000..c3b0d84
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/tests/JsonWebKeyUnittest.cpp
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <utils/Log.h>
+
+#include "JsonWebKey.h"
+
+#include "gtest/gtest.h"
+#include "Utils.h"
+
+namespace clearkeydrm {
+using android::String8;
+using android::Vector;
+
+class JsonWebKeyTest : public ::testing::Test {
+ protected:
+    JsonWebKey* jwk;
+
+    JsonWebKeyTest() {
+        jwk = new JsonWebKey;
+    }
+
+    virtual ~JsonWebKeyTest() {
+        if (jwk)
+            delete jwk;
+    }
+};
+
+void stringFromVector(const Vector<uint8_t>& input,
+        String8* converted) {
+    converted->clear();
+    if (input.isEmpty()) {
+        return;
+    }
+
+    for (size_t i = 0; i < input.size(); ++i) {
+        converted->appendFormat("%c", input.itemAt(i));
+    }
+}
+
+void verifyKeys(const KeyMap& keys, const String8* clearKeys) {
+    if (keys.isEmpty()) {
+        return;
+    }
+
+    String8 keyString;
+    for (size_t i = 0; i < keys.size(); ++i) {
+        stringFromVector(keys.valueAt(i), &keyString);
+        EXPECT_EQ(keyString, clearKeys[i]);
+    }
+}
+
+TEST_F(JsonWebKeyTest, NoSymmetricKey) {
+    const String8 js(
+            "{"
+                "[{"
+                    "\"kty\":\"rsa\","
+                    "\"alg\":\"A128KW1\","
+                    "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\","
+                    "\"k\":\"1-GawgguFyGrWKav7AX4VKUg\""
+                "}]"
+          "}");
+
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, NoKeysTag) {
+    const String8 js(
+            "{"
+                "[{"
+                    "\"kty\":\"oct\","
+                    "\"alg\":\"A128KW1\","
+                    "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\","
+                    "\"k\":\"1-GawgguFyGrWKav7AX4VKUg\""
+                "},"
+                "{"
+                    "\"kty\":\"oct\","
+                    "\"alg\":\"A128KW2\","
+                    "\"k\":\"R29vZCBkYXkh\","
+                    "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, NoKeyId) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kISE=\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.size() == 1);
+
+    const String8 clearKeys("Good day!");
+    verifyKeys(keys, &clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, NoKey) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"kid\":\"`\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.size() == 1);
+
+    const String8 clearKeys("Good day!");
+    verifyKeys(keys, &clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, MalformedKey) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"GawgguFyGrWKav7AX4V???\""
+                        "\"kid\":\"67ef0gd8pvfd0=\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"GawgguFyGrWKav7AX4V???\""
+                        "\"kid\":"
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        ":\"GawgguFyGrWKav7AX4V???\""
+                        "\"kid\":\"67ef0gd8pvfd0=\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_TRUE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.size() == 1);
+
+    const String8 clearKeys("Good day!");
+    verifyKeys(keys, &clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, EmptyJsonWebKey) {
+    const String8 js;
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, MalformedJsonWebKey) {
+    // Missing begin array '['
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"GawgguFyGrWKav7AX4VKUg\""
+                        "\"kid\":\"67ef0gd8pvfd0=\""
+                    "}"
+            "]"
+            "}");
+
+    KeyMap keys;
+    EXPECT_FALSE(jwk->extractKeysFromJsonWebKeySet(js, &keys));
+    EXPECT_TRUE(keys.isEmpty());
+}
+
+TEST_F(JsonWebKeyTest, SameKeyId) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kISE\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kIQ\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    jwk->extractKeysFromJsonWebKeySet(js, &keys);
+    EXPECT_TRUE(keys.size() == 2);
+
+    const String8 clearKeys[] =
+            { String8("Hello Friend!"), String8("Good day!") };
+    verifyKeys(keys, clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, ExtractWellFormedKeys) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kIQ\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    jwk->extractKeysFromJsonWebKeySet(js, &keys);
+    EXPECT_TRUE(keys.size() == 2);
+
+    const String8 clearKeys[] =
+            { String8("Hello Friend!"), String8("Good day!") };
+    verifyKeys(keys, clearKeys);
+}
+
+TEST_F(JsonWebKeyTest, ExtractKeys) {
+    const String8 js(
+            "{"
+                "\"keys\":"
+                    "[{"
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAx\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kISE\""
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW1\""
+                    "}"
+                    "{"
+                        "\"kty\":\"oct\""
+                        "\"alg\":\"A128KW2\""
+                        "\"k\":\"SGVsbG8gRnJpZW5kIQ\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\""
+                    "}"
+                    "{"
+                        "\"kty\":\"rsa\""
+                        "\"alg\":\"A128KW-rsa\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kid\":\"rsa-67ef0gd8pvfd0=\""
+                    "}"
+                    "{"
+                        "\"alg\":\"A128KW3\""
+                        "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\""
+                        "\"k\":\"R29vZCBkYXkh\""
+                        "\"kty\":\"oct\""
+                    "}]"
+            "}");
+
+    KeyMap keys;
+    jwk->extractKeysFromJsonWebKeySet(js, &keys);
+    EXPECT_TRUE(keys.size() == 3);
+
+    const String8 clearKeys[] =
+            { String8("Hello Friend!!"), String8("Hello Friend!"),
+              String8("Good day!") };
+    verifyKeys(keys, clearKeys);
+}
+
+}  // namespace clearkeydrm
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index 3492520..a3cc396 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -188,7 +188,8 @@
                                     transfer_type transferType = TRANSFER_DEFAULT,
                                     const audio_offload_info_t *offloadInfo = NULL,
                                     int uid = -1,
-                                    pid_t pid = -1);
+                                    pid_t pid = -1,
+                                    const audio_attributes_t* pAttributes = NULL);
 
     /* Creates an audio track and registers it with AudioFlinger.
      * With this constructor, the track is configured for static buffer mode.
@@ -214,7 +215,8 @@
                                     transfer_type transferType = TRANSFER_DEFAULT,
                                     const audio_offload_info_t *offloadInfo = NULL,
                                     int uid = -1,
-                                    pid_t pid = -1);
+                                    pid_t pid = -1,
+                                    const audio_attributes_t* pAttributes = NULL);
 
     /* Terminates the AudioTrack and unregisters it from AudioFlinger.
      * Also destroys all resources associated with the AudioTrack.
@@ -254,7 +256,7 @@
                             const audio_offload_info_t *offloadInfo = NULL,
                             int uid = -1,
                             pid_t pid = -1,
-                            audio_attributes_t* pAttributes = NULL);
+                            const audio_attributes_t* pAttributes = NULL);
 
     /* Result of constructing the AudioTrack. This must be checked for successful initialization
      * before using any AudioTrack API (except for set()), because using
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 3ca3095..e756368 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -160,6 +160,9 @@
     // Playback rate expressed in permille (1000 is normal speed), saved as int32_t, with negative
     // values used for rewinding or reverse playback.
     KEY_PARAMETER_PLAYBACK_RATE_PERMILLE = 1300,                // set only
+
+    // Set a Parcel containing the value of a parcelled Java AudioAttribute instance
+    KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400                       // set only
 };
 
 // Keep INVOKE_ID_* in sync with MediaPlayer.java.
@@ -169,7 +172,7 @@
     INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3,
     INVOKE_ID_SELECT_TRACK = 4,
     INVOKE_ID_UNSELECT_TRACK = 5,
-    INVOKE_ID_SET_VIDEO_SCALING_MODE = 6,
+    INVOKE_ID_SET_VIDEO_SCALING_MODE = 6
 };
 
 // Keep MEDIA_TRACK_TYPE_* in sync with MediaPlayer.java.
@@ -259,6 +262,7 @@
             status_t        attachNewPlayer(const sp<IMediaPlayer>& player);
             status_t        reset_l();
             status_t        doSetRetransmitEndpoint(const sp<IMediaPlayer>& player);
+            status_t        checkStateForKeySet_l(int key);
 
     sp<IMediaPlayer>            mPlayer;
     thread_id_t                 mLockThreadId;
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 898d58d..b5c9125 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -124,7 +124,8 @@
         transfer_type transferType,
         const audio_offload_info_t *offloadInfo,
         int uid,
-        pid_t pid)
+        pid_t pid,
+        const audio_attributes_t* pAttributes)
     : mStatus(NO_INIT),
       mIsTimed(false),
       mPreviousPriority(ANDROID_PRIORITY_NORMAL),
@@ -134,7 +135,7 @@
     mStatus = set(streamType, sampleRate, format, channelMask,
             frameCount, flags, cbf, user, notificationFrames,
             0 /*sharedBuffer*/, false /*threadCanCallJava*/, sessionId, transferType,
-            offloadInfo, uid, pid, NULL /*no audio attributes*/);
+            offloadInfo, uid, pid, pAttributes);
 }
 
 AudioTrack::AudioTrack(
@@ -151,7 +152,8 @@
         transfer_type transferType,
         const audio_offload_info_t *offloadInfo,
         int uid,
-        pid_t pid)
+        pid_t pid,
+        const audio_attributes_t* pAttributes)
     : mStatus(NO_INIT),
       mIsTimed(false),
       mPreviousPriority(ANDROID_PRIORITY_NORMAL),
@@ -161,7 +163,7 @@
     mStatus = set(streamType, sampleRate, format, channelMask,
             0 /*frameCount*/, flags, cbf, user, notificationFrames,
             sharedBuffer, false /*threadCanCallJava*/, sessionId, transferType, offloadInfo,
-            uid, pid, NULL /*no audio attributes*/);
+            uid, pid, pAttributes);
 }
 
 AudioTrack::~AudioTrack()
@@ -205,7 +207,7 @@
         const audio_offload_info_t *offloadInfo,
         int uid,
         pid_t pid,
-        audio_attributes_t* pAttributes)
+        const audio_attributes_t* pAttributes)
 {
     ALOGV("set(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, "
           "flags #%x, notificationFrames %u, sessionId %d, transferType %d",
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 406f9f2..889bd7f 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -624,10 +624,32 @@
     return mPlayer->attachAuxEffect(effectId);
 }
 
+// always call with lock held
+status_t MediaPlayer::checkStateForKeySet_l(int key)
+{
+    switch(key) {
+    case KEY_PARAMETER_AUDIO_ATTRIBUTES:
+        if (mCurrentState & ( MEDIA_PLAYER_PREPARED | MEDIA_PLAYER_STARTED |
+                MEDIA_PLAYER_PAUSED | MEDIA_PLAYER_PLAYBACK_COMPLETE) ) {
+            // Can't change the audio attributes after prepare
+            ALOGE("trying to set audio attributes called in state %d", mCurrentState);
+            return INVALID_OPERATION;
+        }
+        break;
+    default:
+        // parameter doesn't require player state check
+        break;
+    }
+    return OK;
+}
+
 status_t MediaPlayer::setParameter(int key, const Parcel& request)
 {
     ALOGV("MediaPlayer::setParameter(%d)", key);
     Mutex::Autolock _l(mLock);
+    if (checkStateForKeySet_l(key) != OK) {
+        return INVALID_OPERATION;
+    }
     if (mPlayer != NULL) {
         return  mPlayer->setParameter(key, request);
     }
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 76632a7..7218467 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -186,6 +186,60 @@
 }  // anonymous namespace
 
 
+namespace {
+using android::Parcel;
+using android::String16;
+
+// marshalling tag indicating flattened utf16 tags
+// keep in sync with frameworks/base/media/java/android/media/AudioAttributes.java
+const int32_t kAudioAttributesMarshallTagFlattenTags = 1;
+
+// Audio attributes format in a parcel:
+//
+//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                       usage                                   |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                       content_type                            |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                       flags                                   |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                       kAudioAttributesMarshallTagFlattenTags  | // ignore tags if not found
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                       flattened tags in UTF16                 |
+// |                         ...                                   |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// @param p Parcel that contains audio attributes.
+// @param[out] attributes On exit points to an initialized audio_attributes_t structure
+// @param[out] status On exit contains the status code to be returned.
+void unmarshallAudioAttributes(const Parcel& parcel, audio_attributes_t *attributes)
+{
+    attributes->usage = (audio_usage_t) parcel.readInt32();
+    attributes->content_type = (audio_content_type_t) parcel.readInt32();
+    attributes->flags = (audio_flags_mask_t) parcel.readInt32();
+    const bool hasFlattenedTag = (parcel.readInt32() == kAudioAttributesMarshallTagFlattenTags);
+    if (hasFlattenedTag) {
+        // the tags are UTF16, convert to UTF8
+        String16 tags = parcel.readString16();
+        ssize_t realTagSize = utf16_to_utf8_length(tags.string(), tags.size());
+        if (realTagSize <= 0) {
+            strcpy(attributes->tags, "");
+        } else {
+            // copy the flattened string into the attributes as the destination for the conversion:
+            // copying array size -1, array for tags was calloc'd, no need to NULL-terminate it
+            size_t tagSize = realTagSize > AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1 ?
+                    AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1 : realTagSize;
+            utf16_to_utf8(tags.string(), tagSize, attributes->tags);
+        }
+    } else {
+        ALOGE("unmarshallAudioAttributes() received unflattened tags, ignoring tag values");
+        strcpy(attributes->tags, "");
+    }
+}
+} // anonymous namespace
+
+
 namespace android {
 
 static bool checkPermission(const char* permissionString) {
@@ -508,6 +562,7 @@
     mAudioSessionId = audioSessionId;
     mUID = uid;
     mRetransmitEndpointValid = false;
+    mAudioAttributes = NULL;
 
 #if CALLBACK_ANTAGONIZER
     ALOGD("create Antagonizer");
@@ -522,6 +577,9 @@
     wp<Client> client(this);
     disconnect();
     mService->removeClient(client);
+    if (mAudioAttributes != NULL) {
+        free(mAudioAttributes);
+    }
 }
 
 void MediaPlayerService::Client::disconnect()
@@ -587,7 +645,7 @@
 
     if (!p->hardwareOutput()) {
         mAudioOutput = new AudioOutput(mAudioSessionId, IPCThreadState::self()->getCallingUid(),
-                mPid);
+                mPid, mAudioAttributes);
         static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
     }
 
@@ -968,6 +1026,22 @@
     return NO_ERROR;
 }
 
+status_t MediaPlayerService::Client::setAudioAttributes_l(const Parcel &parcel)
+{
+    if (mAudioAttributes != NULL) { free(mAudioAttributes); }
+    mAudioAttributes = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
+    unmarshallAudioAttributes(parcel, mAudioAttributes);
+
+    ALOGV("setAudioAttributes_l() usage=%d content=%d flags=0x%x tags=%s",
+            mAudioAttributes->usage, mAudioAttributes->content_type, mAudioAttributes->flags,
+            mAudioAttributes->tags);
+
+    if (mAudioOutput != 0) {
+        mAudioOutput->setAudioAttributes(mAudioAttributes);
+    }
+    return NO_ERROR;
+}
+
 status_t MediaPlayerService::Client::setLooping(int loop)
 {
     ALOGV("[%d] setLooping(%d)", mConnId, loop);
@@ -1016,9 +1090,17 @@
 
 status_t MediaPlayerService::Client::setParameter(int key, const Parcel &request) {
     ALOGV("[%d] setParameter(%d)", mConnId, key);
-    sp<MediaPlayerBase> p = getPlayer();
-    if (p == 0) return UNKNOWN_ERROR;
-    return p->setParameter(key, request);
+    switch (key) {
+    case KEY_PARAMETER_AUDIO_ATTRIBUTES:
+    {
+        Mutex::Autolock l(mLock);
+        return setAudioAttributes_l(request);
+    }
+    default:
+        sp<MediaPlayerBase> p = getPlayer();
+        if (p == 0) { return UNKNOWN_ERROR; }
+        return p->setParameter(key, request);
+    }
 }
 
 status_t MediaPlayerService::Client::getParameter(int key, Parcel *reply) {
@@ -1300,7 +1382,8 @@
 
 #undef LOG_TAG
 #define LOG_TAG "AudioSink"
-MediaPlayerService::AudioOutput::AudioOutput(int sessionId, int uid, int pid)
+MediaPlayerService::AudioOutput::AudioOutput(int sessionId, int uid, int pid,
+        const audio_attributes_t* attr)
     : mCallback(NULL),
       mCallbackCookie(NULL),
       mCallbackData(NULL),
@@ -1319,6 +1402,7 @@
     mAuxEffectId = 0;
     mSendLevel = 0.0;
     setMinBufferCount();
+    mAttributes = attr;
 }
 
 MediaPlayerService::AudioOutput::~AudioOutput()
@@ -1408,6 +1492,10 @@
     return mTrack->getParameters(keys);
 }
 
+void MediaPlayerService::AudioOutput::setAudioAttributes(const audio_attributes_t * attributes) {
+    mAttributes = attributes;
+}
+
 void MediaPlayerService::AudioOutput::deleteRecycledTrack()
 {
     ALOGV("deleteRecycledTrack");
@@ -1557,7 +1645,8 @@
                     AudioTrack::TRANSFER_CALLBACK,
                     offloadInfo,
                     mUid,
-                    mPid);
+                    mPid,
+                    mAttributes);
         } else {
             t = new AudioTrack(
                     mStreamType,
@@ -1573,13 +1662,18 @@
                     AudioTrack::TRANSFER_DEFAULT,
                     NULL, // offload info
                     mUid,
-                    mPid);
+                    mPid,
+                    mAttributes);
         }
 
         if ((t == 0) || (t->initCheck() != NO_ERROR)) {
             ALOGE("Unable to create audio track");
             delete newcbd;
             return NO_INIT;
+        } else {
+            // successful AudioTrack initialization implies a legacy stream type was generated
+            // from the audio attributes
+            mStreamType = t->streamType();
         }
     }
 
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 448f27a..2eca6a0 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -72,7 +72,8 @@
         class CallbackData;
 
      public:
-                                AudioOutput(int sessionId, int uid, int pid);
+                                AudioOutput(int sessionId, int uid, int pid,
+                                        const audio_attributes_t * attr);
         virtual                 ~AudioOutput();
 
         virtual bool            ready() const { return mTrack != 0; }
@@ -104,6 +105,7 @@
                 void            setAudioStreamType(audio_stream_type_t streamType) {
                                                                         mStreamType = streamType; }
         virtual audio_stream_type_t getAudioStreamType() const { return mStreamType; }
+                void            setAudioAttributes(const audio_attributes_t * attributes);
 
                 void            setVolume(float left, float right);
         virtual status_t        setPlaybackRatePermille(int32_t ratePermille);
@@ -133,6 +135,7 @@
         CallbackData *          mCallbackData;
         uint64_t                mBytesWritten;
         audio_stream_type_t     mStreamType;
+        const audio_attributes_t *mAttributes;
         float                   mLeftVolume;
         float                   mRightVolume;
         int32_t                 mPlaybackRatePermille;
@@ -410,6 +413,8 @@
         // Disconnect from the currently connected ANativeWindow.
         void disconnectNativeWindow();
 
+        status_t setAudioAttributes_l(const Parcel &request);
+
         mutable     Mutex                       mLock;
                     sp<MediaPlayerBase>         mPlayer;
                     sp<MediaPlayerService>      mService;
@@ -420,6 +425,7 @@
                     bool                        mLoop;
                     int32_t                     mConnId;
                     int                         mAudioSessionId;
+                    audio_attributes_t *        mAudioAttributes;
                     uid_t                       mUID;
                     sp<ANativeWindow>           mConnectedWindow;
                     sp<IBinder>                 mConnectedWindowBinder;
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index d9e39ff..11c5970 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -68,6 +68,7 @@
         $(TOP)/external/flac/include \
         $(TOP)/external/tremolo \
         $(TOP)/external/openssl/include \
+        $(TOP)/external/libvpx/libwebm \
 
 LOCAL_SHARED_LIBRARIES := \
         libbinder \
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index d7bec59..2587ec7 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -20,8 +20,6 @@
 
 #include "MatroskaExtractor.h"
 
-#include "mkvparser.hpp"
-
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/DataSource.h>
@@ -89,7 +87,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 struct BlockIterator {
-    BlockIterator(MatroskaExtractor *extractor, unsigned long trackNum);
+    BlockIterator(MatroskaExtractor *extractor, unsigned long trackNum, unsigned long index);
 
     bool eos() const;
 
@@ -106,6 +104,7 @@
 private:
     MatroskaExtractor *mExtractor;
     long long mTrackNum;
+    unsigned long mIndex;
 
     const mkvparser::Cluster *mCluster;
     const mkvparser::BlockEntry *mBlockEntry;
@@ -157,6 +156,53 @@
     MatroskaSource &operator=(const MatroskaSource &);
 };
 
+const mkvparser::Track* MatroskaExtractor::TrackInfo::getTrack() const {
+    return mExtractor->mSegment->GetTracks()->GetTrackByNumber(mTrackNum);
+}
+
+// This function does exactly the same as mkvparser::Cues::Find, except that it
+// searches in our own track based vectors. We should not need this once mkvparser
+// adds the same functionality.
+const mkvparser::CuePoint::TrackPosition *MatroskaExtractor::TrackInfo::find(
+        long long timeNs) const {
+    ALOGV("mCuePoints.size %zu", mCuePoints.size());
+    if (mCuePoints.empty()) {
+        return NULL;
+    }
+
+    const mkvparser::CuePoint* cp = mCuePoints.itemAt(0);
+    const mkvparser::Track* track = getTrack();
+    if (timeNs <= cp->GetTime(mExtractor->mSegment)) {
+        return cp->Find(track);
+    }
+
+    // Binary searches through relevant cues; assumes cues are ordered by timecode.
+    // If we do detect out-of-order cues, return NULL.
+    size_t lo = 0;
+    size_t hi = mCuePoints.size();
+    while (lo < hi) {
+        const size_t mid = lo + (hi - lo) / 2;
+        const mkvparser::CuePoint* const midCp = mCuePoints.itemAt(mid);
+        const long long cueTimeNs = midCp->GetTime(mExtractor->mSegment);
+        if (cueTimeNs <= timeNs) {
+            lo = mid + 1;
+        } else {
+            hi = mid;
+        }
+    }
+
+    if (lo == 0) {
+        return NULL;
+    }
+
+    cp = mCuePoints.itemAt(lo - 1);
+    if (cp->GetTime(mExtractor->mSegment) > timeNs) {
+        return NULL;
+    }
+
+    return cp->Find(track);
+}
+
 MatroskaSource::MatroskaSource(
         const sp<MatroskaExtractor> &extractor, size_t index)
     : mExtractor(extractor),
@@ -164,7 +210,8 @@
       mType(OTHER),
       mIsAudio(false),
       mBlockIter(mExtractor.get(),
-                 mExtractor->mTracks.itemAt(index).mTrackNum),
+                 mExtractor->mTracks.itemAt(index).mTrackNum,
+                 index),
       mNALSizeLen(0) {
     sp<MetaData> meta = mExtractor->mTracks.itemAt(index).mMeta;
 
@@ -214,9 +261,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 BlockIterator::BlockIterator(
-        MatroskaExtractor *extractor, unsigned long trackNum)
+        MatroskaExtractor *extractor, unsigned long trackNum, unsigned long index)
     : mExtractor(extractor),
       mTrackNum(trackNum),
+      mIndex(index),
       mCluster(NULL),
       mBlockEntry(NULL),
       mBlockEntryIndex(0) {
@@ -364,9 +412,20 @@
     }
 
     const mkvparser::CuePoint* pCP;
+    mkvparser::Tracks const *pTracks = pSegment->GetTracks();
+    unsigned long int trackCount = pTracks->GetTracksCount();
     while (!pCues->DoneParsing()) {
         pCues->LoadCuePoint();
         pCP = pCues->GetLast();
+        CHECK(pCP);
+
+        for (size_t index = 0; index < trackCount; ++index) {
+            const mkvparser::Track *pTrack = pTracks->GetTrackByIndex(index);
+            if (pTrack && pTrack->GetType() == 1 && pCP->Find(pTrack)) { // VIDEO_TRACK
+                MatroskaExtractor::TrackInfo& track = mExtractor->mTracks.editItemAt(index);
+                track.mCuePoints.push_back(pCP);
+            }
+        }
 
         if (pCP->GetTime(pSegment) >= seekTimeNs) {
             ALOGV("Parsed past relevant Cue");
@@ -374,22 +433,25 @@
         }
     }
 
-    // The Cue index is built around video keyframes
-    mkvparser::Tracks const *pTracks = pSegment->GetTracks();
-    const mkvparser::Track *pTrack = NULL;
-    for (size_t index = 0; index < pTracks->GetTracksCount(); ++index) {
-        pTrack = pTracks->GetTrackByIndex(index);
-        if (pTrack && pTrack->GetType() == 1) { // VIDEO_TRACK
-            ALOGV("Video track located at %zu", index);
-            break;
+    const mkvparser::CuePoint::TrackPosition *pTP = NULL;
+    const mkvparser::Track *thisTrack = pTracks->GetTrackByIndex(mIndex);
+    if (thisTrack->GetType() == 1) { // video
+        MatroskaExtractor::TrackInfo& track = mExtractor->mTracks.editItemAt(mIndex);
+        pTP = track.find(seekTimeNs);
+    } else {
+        // The Cue index is built around video keyframes
+        for (size_t index = 0; index < trackCount; ++index) {
+            const mkvparser::Track *pTrack = pTracks->GetTrackByIndex(index);
+            if (pTrack && pTrack->GetType() == 1 && pCues->Find(seekTimeNs, pTrack, pCP, pTP)) {
+                ALOGV("Video track located at %zu", index);
+                break;
+            }
         }
     }
 
+
     // Always *search* based on the video track, but finalize based on mTrackNum
-    const mkvparser::CuePoint::TrackPosition* pTP;
-    if (pTrack && pTrack->GetType() == 1) {
-        pCues->Find(seekTimeNs, pTrack, pCP, pTP);
-    } else {
+    if (!pTP) {
         ALOGE("Did not locate the video track for seeking");
         return;
     }
@@ -410,10 +472,13 @@
 
         if (isAudio || block()->IsKey()) {
             // Accept the first key frame
-            *actualFrameTimeUs = (block()->GetTime(mCluster) + 500LL) / 1000LL;
-            ALOGV("Requested seek point: %" PRId64 " actual: %" PRId64,
-                  seekTimeUs, *actualFrameTimeUs);
-            break;
+            int64_t frameTimeUs = (block()->GetTime(mCluster) + 500LL) / 1000LL;
+            if (thisTrack->GetType() == 1 || frameTimeUs >= seekTimeUs) {
+                *actualFrameTimeUs = frameTimeUs;
+                ALOGV("Requested seek point: %" PRId64 " actual: %" PRId64,
+                      seekTimeUs, *actualFrameTimeUs);
+                break;
+            }
         }
     }
 }
@@ -964,6 +1029,7 @@
         TrackInfo *trackInfo = &mTracks.editItemAt(mTracks.size() - 1);
         trackInfo->mTrackNum = track->GetNumber();
         trackInfo->mMeta = meta;
+        trackInfo->mExtractor = this;
     }
 }
 
@@ -978,7 +1044,7 @@
             continue;
         }
 
-        BlockIterator iter(this, info->mTrackNum);
+        BlockIterator iter(this, info->mTrackNum, i);
         int32_t j = 0;
         int64_t thumbnailTimeUs = 0;
         size_t maxBlockSize = 0;
diff --git a/media/libstagefright/matroska/MatroskaExtractor.h b/media/libstagefright/matroska/MatroskaExtractor.h
index cf200f3..db36bf8 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.h
+++ b/media/libstagefright/matroska/MatroskaExtractor.h
@@ -18,14 +18,12 @@
 
 #define MATROSKA_EXTRACTOR_H_
 
+#include "mkvparser.hpp"
+
 #include <media/stagefright/MediaExtractor.h>
 #include <utils/Vector.h>
 #include <utils/threads.h>
 
-namespace mkvparser {
-struct Segment;
-};
-
 namespace android {
 
 struct AMessage;
@@ -58,6 +56,11 @@
     struct TrackInfo {
         unsigned long mTrackNum;
         sp<MetaData> mMeta;
+        const MatroskaExtractor *mExtractor;
+        Vector<const mkvparser::CuePoint*> mCuePoints;
+
+        const mkvparser::Track* getTrack() const;
+        const mkvparser::CuePoint::TrackPosition *find(long long timeNs) const;
     };
 
     Mutex mLock;
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 527fd65..11a01cc 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1621,6 +1621,19 @@
     mHardwareStatus = AUDIO_HW_OUTPUT_OPEN;
 
     audio_stream_out_t *outStream = NULL;
+
+    // FOR TESTING ONLY:
+    // Enable increased sink precision for mixing mode if kEnableExtendedPrecision is true.
+    if (kEnableExtendedPrecision &&  // Check only for Normal Mixing mode
+            !(flags & (AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD | AUDIO_OUTPUT_FLAG_DIRECT))) {
+        // Update format
+        //config.format = AUDIO_FORMAT_PCM_FLOAT;
+        //config.format = AUDIO_FORMAT_PCM_24_BIT_PACKED;
+        //config.format = AUDIO_FORMAT_PCM_32_BIT;
+        //config.format = AUDIO_FORMAT_PCM_8_24_BIT;
+        // ALOGV("openOutput() upgrading format to %#08x", config.format);
+    }
+
     status_t status = hwDevHal->open_output_stream(hwDevHal,
                                           id,
                                           *pDevices,
@@ -1644,9 +1657,9 @@
         if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
             thread = new OffloadThread(this, output, id, *pDevices);
             ALOGV("openOutput() created offload output: ID %d thread %p", id, thread);
-        } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT) ||
-            (config.format != AUDIO_FORMAT_PCM_16_BIT) ||
-            (config.channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {
+        } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
+                || !isValidPcmSinkFormat(config.format)
+                || (config.channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {
             thread = new DirectOutputThread(this, output, id, *pDevices);
             ALOGV("openOutput() created direct output: ID %d thread %p", id, thread);
         } else {
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 6e73a14..ddc6afb 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -326,6 +326,24 @@
                                                 audio_devices_t devices);
     void                    purgeStaleEffects_l();
 
+    // Set kEnableExtendedPrecision to true to use extended precision in MixerThread
+    static const bool kEnableExtendedPrecision = false;
+
+    // Returns true if format is permitted for the PCM sink in the MixerThread
+    static inline bool isValidPcmSinkFormat(audio_format_t format) {
+        switch (format) {
+        case AUDIO_FORMAT_PCM_16_BIT:
+            return true;
+        case AUDIO_FORMAT_PCM_FLOAT:
+        case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+        case AUDIO_FORMAT_PCM_32_BIT:
+        case AUDIO_FORMAT_PCM_8_24_BIT:
+            return kEnableExtendedPrecision;
+        default:
+            return false;
+        }
+    }
+
     // standby delay for MIXER and DUPLICATING playback threads is read from property
     // ro.audio.flinger_standbytime_ms or defaults to kDefaultStandbyTimeInNsecs
     static nsecs_t          mStandbyTimeInNsecs;
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index d73292e..af312c4 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -40,8 +40,36 @@
 
 #include <media/EffectsFactoryApi.h>
 
+#include "AudioMixerOps.h"
 #include "AudioMixer.h"
 
+// Use the FCC_2 macro for code assuming Fixed Channel Count of 2 and
+// whose stereo assumption may need to be revisited later.
+#ifndef FCC_2
+#define FCC_2 2
+#endif
+
+/* VERY_VERY_VERBOSE_LOGGING will show exactly which process hook and track hook is
+ * being used. This is a considerable amount of log spam, so don't enable unless you
+ * are verifying the hook based code.
+ */
+//#define VERY_VERY_VERBOSE_LOGGING
+#ifdef VERY_VERY_VERBOSE_LOGGING
+#define ALOGVV ALOGV
+//define ALOGVV printf  // for test-mixer.cpp
+#else
+#define ALOGVV(a...) do { } while (0)
+#endif
+
+// Set kUseNewMixer to true to use the new mixer engine. Otherwise the
+// original code will be used.  This is false for now.
+static const bool kUseNewMixer = false;
+
+// Set kUseFloat to true to allow floating input into the mixer engine.
+// If kUseNewMixer is false, this is ignored or may be overridden internally
+// because of downmix/upmix support.
+static const bool kUseFloat = true;
+
 namespace android {
 
 // ----------------------------------------------------------------------------
@@ -300,15 +328,19 @@
         t->downmixerBufferProvider = NULL;
         t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
         t->mFormat = format;
-        t->mMixerInFormat = AUDIO_FORMAT_PCM_16_BIT;
-        if (t->mFormat != t->mMixerInFormat) {
-            prepareTrackForReformat(t, n);
-        }
-        status_t status = initTrackDownmix(&mState.tracks[n], n, channelMask);
+        t->mMixerInFormat = kUseFloat && kUseNewMixer
+                ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+        // Check the downmixing (or upmixing) requirements.
+        status_t status = initTrackDownmix(t, n, channelMask);
         if (status != OK) {
             ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask);
             return -1;
         }
+        // initTrackDownmix() may change the input format requirement.
+        // If you desire floating point input to the mixer, it may change
+        // to integer because the downmixer requires integer to process.
+        ALOGVV("mMixerFormat:%#x  mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
+        prepareTrackForReformat(t, n);
         mTrackNames |= 1 << n;
         return TRACK0 + n;
     }
@@ -443,6 +475,7 @@
     }// end of scope for local variables that are not used in goto label "noDownmixForActiveTrack"
 
     // initialization successful:
+    pTrack->mMixerInFormat = AUDIO_FORMAT_PCM_16_BIT; // 16 bit input is required for downmix
     pTrack->downmixerBufferProvider = pDbp;
     reconfigureBufferProviders(pTrack);
     return NO_ERROR;
@@ -467,12 +500,15 @@
 {
     ALOGV("AudioMixer::prepareTrackForReformat(%d) with format %#x", trackName, pTrack->mFormat);
     // discard the previous reformatter if there was one
-     unprepareTrackForReformat(pTrack, trackName);
-     pTrack->mReformatBufferProvider = new ReformatBufferProvider(
-             audio_channel_count_from_out_mask(pTrack->channelMask),
-             pTrack->mFormat, pTrack->mMixerInFormat);
-     reconfigureBufferProviders(pTrack);
-     return NO_ERROR;
+    unprepareTrackForReformat(pTrack, trackName);
+    // only configure reformatter if needed
+    if (pTrack->mFormat != pTrack->mMixerInFormat) {
+        pTrack->mReformatBufferProvider = new ReformatBufferProvider(
+                audio_channel_count_from_out_mask(pTrack->channelMask),
+                pTrack->mFormat, pTrack->mMixerInFormat);
+        reconfigureBufferProviders(pTrack);
+    }
+    return NO_ERROR;
 }
 
 void AudioMixer::reconfigureBufferProviders(track_t* pTrack)
@@ -596,8 +632,15 @@
                 track.channelMask = mask;
                 track.channelCount = channelCount;
                 // the mask has changed, does this track need a downmixer?
-                initTrackDownmix(&mState.tracks[name], name, mask);
+                // update to try using our desired format (if we aren't already using it)
+                track.mMixerInFormat = kUseFloat && kUseNewMixer
+                        ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+                status_t status = initTrackDownmix(&mState.tracks[name], name, mask);
+                ALOGE_IF(status != OK,
+                        "Invalid channel mask %#x, initTrackDownmix returned %d",
+                        mask, status);
                 ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", mask);
+                prepareTrackForReformat(&track, name); // format may have changed
                 invalidateState(1 << name);
             }
             } break;
@@ -621,11 +664,7 @@
                 ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format);
                 track.mFormat = format;
                 ALOGV("setParameter(TRACK, FORMAT, %#x)", format);
-                //if (track.mFormat != track.mMixerInFormat)
-                {
-                    ALOGD("Reformatting!");
-                    prepareTrackForReformat(&track, name);
-                }
+                prepareTrackForReformat(&track, name);
                 invalidateState(1 << name);
             }
             } break;
@@ -723,7 +762,20 @@
                 } else {
                     quality = AudioResampler::DEFAULT_QUALITY;
                 }
-                const int bits = mMixerInFormat == AUDIO_FORMAT_PCM_16_BIT ? 16 : /* FLOAT */ 32;
+
+                int bits;
+                switch (mMixerInFormat) {
+                case AUDIO_FORMAT_PCM_16_BIT:
+                    bits = 16;
+                    break;
+                case AUDIO_FORMAT_PCM_FLOAT:
+                    bits = 32; // 32 bits to the AudioResampler::create() indicates float.
+                    break;
+                default:
+                    LOG_ALWAYS_FATAL("Invalid mMixerInFormat: %#x", mMixerInFormat);
+                    break;
+                }
+                ALOGVV("Creating resampler with %d bits\n", bits);
                 resampler = AudioResampler::create(
                         bits,
                         // the resampler sees the number of channels after the downmixer, if any
@@ -848,16 +900,19 @@
             if (n & NEEDS_RESAMPLE) {
                 all16BitsStereoNoResample = false;
                 resampling = true;
-                t.hook = track__genericResample;
+                t.hook = getTrackHook(TRACKTYPE_RESAMPLE, FCC_2,
+                        t.mMixerInFormat, t.mMixerFormat);
                 ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
                         "Track %d needs downmix + resample", i);
             } else {
                 if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
-                    t.hook = track__16BitsMono;
+                    t.hook = getTrackHook(TRACKTYPE_NORESAMPLEMONO, FCC_2,
+                            t.mMixerInFormat, t.mMixerFormat);
                     all16BitsStereoNoResample = false;
                 }
                 if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){
-                    t.hook = track__16BitsStereo;
+                    t.hook = getTrackHook(TRACKTYPE_NORESAMPLE, FCC_2,
+                            t.mMixerInFormat, t.mMixerFormat);
                     ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
                             "Track %d needs downmix", i);
                 }
@@ -888,7 +943,10 @@
             state->hook = process__genericNoResampling;
             if (all16BitsStereoNoResample && !volumeRamp) {
                 if (countActiveTracks == 1) {
-                    state->hook = process__OneTrack16BitsStereoNoResampling;
+                    const int i = 31 - __builtin_clz(state->enabledTracks);
+                    track_t& t = state->tracks[i];
+                    state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, FCC_2,
+                            t.mMixerInFormat, t.mMixerFormat);
                 }
             }
         }
@@ -931,6 +989,7 @@
 void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount,
         int32_t* temp, int32_t* aux)
 {
+    ALOGVV("track__genericResample\n");
     t->resampler->setSampleRate(t->sampleRate);
 
     // ramp gain - resample to temp buffer and scale/mix in 2nd step
@@ -1042,6 +1101,7 @@
 void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount,
         int32_t* temp __unused, int32_t* aux)
 {
+    ALOGVV("track__16BitsStereo\n");
     const int16_t *in = static_cast<const int16_t *>(t->in);
 
     if (CC_UNLIKELY(aux != NULL)) {
@@ -1133,6 +1193,7 @@
 void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount,
         int32_t* temp __unused, int32_t* aux)
 {
+    ALOGVV("track__16BitsMono\n");
     const int16_t *in = static_cast<int16_t const *>(t->in);
 
     if (CC_UNLIKELY(aux != NULL)) {
@@ -1220,6 +1281,7 @@
 // no-op case
 void AudioMixer::process__nop(state_t* state, int64_t pts)
 {
+    ALOGVV("process__nop\n");
     uint32_t e0 = state->enabledTracks;
     size_t sampleCount = state->frameCount * MAX_NUM_CHANNELS;
     while (e0) {
@@ -1267,6 +1329,7 @@
 // generic code without resampling
 void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
 {
+    ALOGVV("process__genericNoResampling\n");
     int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
 
     // acquire each track's buffer
@@ -1349,18 +1412,12 @@
                     }
                 }
             }
-            switch (t1.mMixerFormat) {
-            case AUDIO_FORMAT_PCM_FLOAT:
-                memcpy_to_float_from_q4_27(reinterpret_cast<float *>(out), outTemp, BLOCKSIZE * 2);
-                out += BLOCKSIZE * 2; // output is 2 floats/frame.
-                break;
-            case AUDIO_FORMAT_PCM_16_BIT:
-                ditherAndClamp(out, outTemp, BLOCKSIZE);
-                out += BLOCKSIZE; // output is 1 int32_t (2 int16_t samples)/frame
-                break;
-            default:
-                LOG_ALWAYS_FATAL("bad mixer format: %d", t1.mMixerFormat);
-            }
+
+            convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat,
+                    BLOCKSIZE * FCC_2);
+            // TODO: fix ugly casting due to choice of out pointer type
+            out = reinterpret_cast<int32_t*>((uint8_t*)out
+                    + BLOCKSIZE * FCC_2 * audio_bytes_per_sample(t1.mMixerFormat));
             numFrames += BLOCKSIZE;
         } while (numFrames < state->frameCount);
     }
@@ -1379,6 +1436,7 @@
 // generic code with resampling
 void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
 {
+    ALOGVV("process__genericResampling\n");
     // this const just means that local variable outTemp doesn't change
     int32_t* const outTemp = state->outputTemp;
     const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount;
@@ -1442,16 +1500,7 @@
                 }
             }
         }
-        switch (t1.mMixerFormat) {
-        case AUDIO_FORMAT_PCM_FLOAT:
-            memcpy_to_float_from_q4_27(reinterpret_cast<float*>(out), outTemp, numFrames*2);
-            break;
-        case AUDIO_FORMAT_PCM_16_BIT:
-            ditherAndClamp(out, outTemp, numFrames);
-            break;
-        default:
-            LOG_ALWAYS_FATAL("bad mixer format: %d", t1.mMixerFormat);
-        }
+        convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat, numFrames * FCC_2);
     }
 }
 
@@ -1459,6 +1508,7 @@
 void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
                                                            int64_t pts)
 {
+    ALOGVV("process__OneTrack16BitsStereoNoResampling\n");
     // This method is only called when state->enabledTracks has exactly
     // one bit set.  The asserts below would verify this, but are commented out
     // since the whole point of this method is to optimize performance.
@@ -1683,5 +1733,275 @@
     ALOGW_IF(!sIsMultichannelCapable, "unable to find downmix effect");
 }
 
+/* This process hook is called when there is a single track without
+ * aux buffer, volume ramp, or resampling.
+ * TODO: Update the hook selection: this can properly handle aux and ramp.
+ */
+template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+void AudioMixer::process_NoResampleOneTrack(state_t* state, int64_t pts)
+{
+    ALOGVV("process_NoResampleOneTrack\n");
+    // CLZ is faster than CTZ on ARM, though really not sure if true after 31 - clz.
+    const int i = 31 - __builtin_clz(state->enabledTracks);
+    ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled");
+    track_t *t = &state->tracks[i];
+    TO* out = reinterpret_cast<TO*>(t->mainBuffer);
+    TA* aux = reinterpret_cast<TA*>(t->auxBuffer);
+    const bool ramp = t->needsRamp();
+
+    for (size_t numFrames = state->frameCount; numFrames; ) {
+        AudioBufferProvider::Buffer& b(t->buffer);
+        // get input buffer
+        b.frameCount = numFrames;
+        const int64_t outputPTS = calculateOutputPTS(*t, pts, state->frameCount - numFrames);
+        t->bufferProvider->getNextBuffer(&b, outputPTS);
+        const TI *in = reinterpret_cast<TI*>(b.raw);
+
+        // in == NULL can happen if the track was flushed just after having
+        // been enabled for mixing.
+        if (in == NULL || (((uintptr_t)in) & 3)) {
+            memset(out, 0, numFrames
+                    * NCHAN * audio_bytes_per_sample(t->mMixerFormat));
+            ALOGE_IF((((uintptr_t)in) & 3), "process_NoResampleOneTrack: bus error: "
+                    "buffer %p track %p, channels %d, needs %#x",
+                    in, t, t->channelCount, t->needs);
+            return;
+        }
+
+        const size_t outFrames = b.frameCount;
+        if (ramp) {
+            volumeRampMulti<MIXTYPE_MULTI_SAVEONLY, NCHAN>(out, outFrames, in, aux,
+                    t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+        } else {
+            volumeMulti<MIXTYPE_MULTI_SAVEONLY, NCHAN>(out, outFrames, in, aux,
+                    t->volume, t->auxLevel);
+        }
+        out += outFrames * NCHAN;
+        if (aux != NULL) {
+            aux += NCHAN;
+        }
+        numFrames -= b.frameCount;
+
+        // release buffer
+        t->bufferProvider->releaseBuffer(&b);
+    }
+    if (ramp) {
+        t->adjustVolumeRamp(aux != NULL);
+    }
+}
+
+/* This track hook is called to do resampling then mixing,
+ * pulling from the track's upstream AudioBufferProvider.
+ */
+template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+void AudioMixer::track__Resample(track_t* t, TO* out, size_t outFrameCount, TO* temp, TA* aux)
+{
+    ALOGVV("track__Resample\n");
+    t->resampler->setSampleRate(t->sampleRate);
+
+    const bool ramp = t->needsRamp();
+    if (ramp || aux != NULL) {
+        // if ramp:        resample with unity gain to temp buffer and scale/mix in 2nd step.
+        // if aux != NULL: resample with unity gain to temp buffer then apply send level.
+
+        t->resampler->setVolume(UNITY_GAIN_INT, UNITY_GAIN_INT);
+        memset(temp, 0, outFrameCount * NCHAN * sizeof(TO));
+        t->resampler->resample((int32_t*)temp, outFrameCount, t->bufferProvider);
+        if (ramp) {
+            volumeRampMulti<MIXTYPE_MULTI, NCHAN>(out, outFrameCount, temp, aux,
+                    t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+            t->adjustVolumeRamp(aux != NULL);
+        } else {
+            volumeMulti<MIXTYPE_MULTI, NCHAN>(out, outFrameCount, temp, aux,
+                    t->volume, t->auxLevel);
+        }
+    } else { // constant volume gain
+        t->resampler->setVolume(t->volume[0], t->volume[1]);
+        t->resampler->resample((int32_t*)out, outFrameCount, t->bufferProvider);
+    }
+}
+
+/* This track hook is called to mix a track, when no resampling is required.
+ * The input buffer should be present in t->in.
+ */
+template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+void AudioMixer::track__NoResample(track_t* t, TO* out, size_t frameCount,
+        TO* temp __unused, TA* aux)
+{
+    ALOGVV("track__NoResample\n");
+    const TI *in = static_cast<const TI *>(t->in);
+
+    if (t->needsRamp()) {
+        volumeRampMulti<MIXTYPE, NCHAN>(out, frameCount, in, aux,
+                t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+        t->adjustVolumeRamp(aux != NULL);
+    } else {
+        volumeMulti<MIXTYPE, NCHAN>(out, frameCount, in, aux, t->volume, t->auxLevel);
+    }
+    // MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels.
+    // MIXTYPE_MULTI reads NCHAN input channels and places to NCHAN output channels.
+    in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * NCHAN;
+    t->in = in;
+}
+
+/* The Mixer engine generates either int32_t (Q4_27) or float data.
+ * We use this function to convert the engine buffers
+ * to the desired mixer output format, either int16_t (Q.15) or float.
+ */
+void AudioMixer::convertMixerFormat(void *out, audio_format_t mixerOutFormat,
+        void *in, audio_format_t mixerInFormat, size_t sampleCount)
+{
+    switch (mixerInFormat) {
+    case AUDIO_FORMAT_PCM_FLOAT:
+        switch (mixerOutFormat) {
+        case AUDIO_FORMAT_PCM_FLOAT:
+            memcpy(out, in, sampleCount * sizeof(float)); // MEMCPY. TODO optimize out
+            break;
+        case AUDIO_FORMAT_PCM_16_BIT:
+            memcpy_to_i16_from_float((int16_t*)out, (float*)in, sampleCount);
+            break;
+        default:
+            LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+            break;
+        }
+        break;
+    case AUDIO_FORMAT_PCM_16_BIT:
+        switch (mixerOutFormat) {
+        case AUDIO_FORMAT_PCM_FLOAT:
+            memcpy_to_float_from_q4_27((float*)out, (int32_t*)in, sampleCount);
+            break;
+        case AUDIO_FORMAT_PCM_16_BIT:
+            // two int16_t are produced per iteration
+            ditherAndClamp((int32_t*)out, (int32_t*)in, sampleCount >> 1);
+            break;
+        default:
+            LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+            break;
+        }
+        break;
+    default:
+        LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+        break;
+    }
+}
+
+/* Returns the proper track hook to use for mixing the track into the output buffer.
+ */
+AudioMixer::hook_t AudioMixer::getTrackHook(int trackType, int channels,
+        audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused)
+{
+    if (!kUseNewMixer && channels == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
+        switch (trackType) {
+        case TRACKTYPE_NOP:
+            return track__nop;
+        case TRACKTYPE_RESAMPLE:
+            return track__genericResample;
+        case TRACKTYPE_NORESAMPLEMONO:
+            return track__16BitsMono;
+        case TRACKTYPE_NORESAMPLE:
+            return track__16BitsStereo;
+        default:
+            LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
+            break;
+        }
+    }
+    LOG_ALWAYS_FATAL_IF(channels != FCC_2); // TODO: must be stereo right now
+    switch (trackType) {
+    case TRACKTYPE_NOP:
+        return track__nop;
+    case TRACKTYPE_RESAMPLE:
+        switch (mixerInFormat) {
+        case AUDIO_FORMAT_PCM_FLOAT:
+            return (AudioMixer::hook_t)
+                    track__Resample<MIXTYPE_MULTI, 2, float, float, int32_t>;
+        case AUDIO_FORMAT_PCM_16_BIT:
+            return (AudioMixer::hook_t)\
+                    track__Resample<MIXTYPE_MULTI, 2, int32_t, int16_t, int32_t>;
+        default:
+            LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+            break;
+        }
+        break;
+    case TRACKTYPE_NORESAMPLEMONO:
+        switch (mixerInFormat) {
+        case AUDIO_FORMAT_PCM_FLOAT:
+            return (AudioMixer::hook_t)
+                    track__NoResample<MIXTYPE_MONOEXPAND, 2, float, float, int32_t>;
+        case AUDIO_FORMAT_PCM_16_BIT:
+            return (AudioMixer::hook_t)
+                    track__NoResample<MIXTYPE_MONOEXPAND, 2, int32_t, int16_t, int32_t>;
+        default:
+            LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+            break;
+        }
+        break;
+    case TRACKTYPE_NORESAMPLE:
+        switch (mixerInFormat) {
+        case AUDIO_FORMAT_PCM_FLOAT:
+            return (AudioMixer::hook_t)
+                    track__NoResample<MIXTYPE_MULTI, 2, float, float, int32_t>;
+        case AUDIO_FORMAT_PCM_16_BIT:
+            return (AudioMixer::hook_t)
+                    track__NoResample<MIXTYPE_MULTI, 2, int32_t, int16_t, int32_t>;
+        default:
+            LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+            break;
+        }
+        break;
+    default:
+        LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
+        break;
+    }
+    return NULL;
+}
+
+/* Returns the proper process hook for mixing tracks. Currently works only for
+ * PROCESSTYPE_NORESAMPLEONETRACK, a mix involving one track, no resampling.
+ */
+AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, int channels,
+        audio_format_t mixerInFormat, audio_format_t mixerOutFormat)
+{
+    if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK
+        LOG_ALWAYS_FATAL("bad processType: %d", processType);
+        return NULL;
+    }
+    if (!kUseNewMixer && channels == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
+        return process__OneTrack16BitsStereoNoResampling;
+    }
+    LOG_ALWAYS_FATAL_IF(channels != FCC_2); // TODO: must be stereo right now
+    switch (mixerInFormat) {
+    case AUDIO_FORMAT_PCM_FLOAT:
+        switch (mixerOutFormat) {
+        case AUDIO_FORMAT_PCM_FLOAT:
+            return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+                    float, float, int32_t>;
+        case AUDIO_FORMAT_PCM_16_BIT:
+            return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+                    int16_t, float, int32_t>;
+        default:
+            LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+            break;
+        }
+        break;
+    case AUDIO_FORMAT_PCM_16_BIT:
+        switch (mixerOutFormat) {
+        case AUDIO_FORMAT_PCM_FLOAT:
+            return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+                    float, int16_t, int32_t>;
+        case AUDIO_FORMAT_PCM_16_BIT:
+            return process_NoResampleOneTrack<MIXTYPE_MULTI_SAVEONLY, 2,
+                    int16_t, int16_t, int32_t>;
+        default:
+            LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+            break;
+        }
+        break;
+    default:
+        LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+        break;
+    }
+    return NULL;
+}
+
 // ----------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index 766ff60..e6de00c 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -221,6 +221,7 @@
 
         // 16-byte boundary
 
+        bool        needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
         bool        setResampler(uint32_t sampleRate, uint32_t devSampleRate);
         bool        doesResample() const { return resampler != NULL; }
         void        resetResampler() { if (resampler != NULL) resampler->reset(); }
@@ -229,12 +230,14 @@
                                                     resampler->getUnreleasedFrames() : 0; };
     };
 
+    typedef void (*process_hook_t)(state_t* state, int64_t pts);
+
     // pad to 32-bytes to fill cache line
     struct state_t {
         uint32_t        enabledTracks;
         uint32_t        needsChanged;
         size_t          frameCount;
-        void            (*hook)(state_t* state, int64_t pts);   // one of process__*, never NULL
+        process_hook_t  hook;   // one of process__*, never NULL
         int32_t         *outputTemp;
         int32_t         *resampleTemp;
         NBLog::Writer*  mLog;
@@ -345,6 +348,38 @@
     static uint64_t         sLocalTimeFreq;
     static pthread_once_t   sOnceControl;
     static void             sInitRoutine();
+
+    // multi-format process hooks
+    template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+    static void process_NoResampleOneTrack(state_t* state, int64_t pts);
+
+    // multi-format track hooks
+    template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+    static void track__Resample(track_t* t, TO* out, size_t frameCount,
+            TO* temp __unused, TA* aux);
+    template <int MIXTYPE, int NCHAN, typename TO, typename TI, typename TA>
+    static void track__NoResample(track_t* t, TO* out, size_t frameCount,
+            TO* temp __unused, TA* aux);
+
+    static void convertMixerFormat(void *out, audio_format_t mixerOutFormat,
+            void *in, audio_format_t mixerInFormat, size_t sampleCount);
+
+    // hook types
+    enum {
+        PROCESSTYPE_NORESAMPLEONETRACK,
+    };
+    enum {
+        TRACKTYPE_NOP,
+        TRACKTYPE_RESAMPLE,
+        TRACKTYPE_NORESAMPLE,
+        TRACKTYPE_NORESAMPLEMONO,
+    };
+
+    // functions for determining the proper process and track hooks.
+    static process_hook_t getProcessHook(int processType, int channels,
+            audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
+    static hook_t getTrackHook(int trackType, int channels,
+            audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
 };
 
 // ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioMixerOps.h b/services/audioflinger/AudioMixerOps.h
new file mode 100644
index 0000000..de92946
--- /dev/null
+++ b/services/audioflinger/AudioMixerOps.h
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIO_MIXER_OPS_H
+#define ANDROID_AUDIO_MIXER_OPS_H
+
+namespace android {
+
+/* Behavior of is_same<>::value is true if the types are identical,
+ * false otherwise. Identical to the STL std::is_same.
+ */
+template<typename T, typename U>
+struct is_same
+{
+    static const bool value = false;
+};
+
+template<typename T>
+struct is_same<T, T>  // partial specialization
+{
+    static const bool value = true;
+};
+
+
+/* MixMul is a multiplication operator to scale an audio input signal
+ * by a volume gain, with the formula:
+ *
+ * O(utput) = I(nput) * V(olume)
+ *
+ * The output, input, and volume may have different types.
+ * There are 27 variants, of which 14 are actually defined in an
+ * explicitly templated class.
+ *
+ * The following type variables and the underlying meaning:
+ *
+ * Output type       TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
+ * Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
+ * Volume type       TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1]
+ *
+ * For high precision audio, only the <TO, TI, TV> = <float, float, float>
+ * needs to be accelerated. This is perhaps the easiest form to do quickly as well.
+ */
+
+template <typename TO, typename TI, typename TV>
+inline TO MixMul(TI value, TV volume) {
+    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(false);
+    // should not be here :-).
+    // To avoid mistakes, this template is always specialized.
+    return value * volume;
+}
+
+template <>
+inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
+    return value * volume;
+}
+
+template <>
+inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
+    return (value >> 12) * volume;
+}
+
+template <>
+inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
+    return value * (volume >> 16);
+}
+
+template <>
+inline int32_t MixMul<int32_t, int32_t, int32_t>(int32_t value, int32_t volume) {
+    return (value >> 12) * (volume >> 16);
+}
+
+template <>
+inline float MixMul<float, float, int16_t>(float value, int16_t volume) {
+    static const float norm = 1. / (1 << 12);
+    return value * volume * norm;
+}
+
+template <>
+inline float MixMul<float, float, int32_t>(float value, int32_t volume) {
+    static const float norm = 1. / (1 << 28);
+    return value * volume * norm;
+}
+
+template <>
+inline int16_t MixMul<int16_t, float, int16_t>(float value, int16_t volume) {
+    return clamp16_from_float(MixMul<float, float, int16_t>(value, volume));
+}
+
+template <>
+inline int16_t MixMul<int16_t, float, int32_t>(float value, int32_t volume) {
+    return clamp16_from_float(MixMul<float, float, int32_t>(value, volume));
+}
+
+template <>
+inline float MixMul<float, int16_t, int16_t>(int16_t value, int16_t volume) {
+    static const float norm = 1. / (1 << (15 + 12));
+    return static_cast<float>(value) * static_cast<float>(volume) * norm;
+}
+
+template <>
+inline float MixMul<float, int16_t, int32_t>(int16_t value, int32_t volume) {
+    static const float norm = 1. / (1ULL << (15 + 28));
+    return static_cast<float>(value) * static_cast<float>(volume) * norm;
+}
+
+template <>
+inline int16_t MixMul<int16_t, int16_t, int16_t>(int16_t value, int16_t volume) {
+    return clamp16(MixMul<int32_t, int16_t, int16_t>(value, volume) >> 12);
+}
+
+template <>
+inline int16_t MixMul<int16_t, int32_t, int16_t>(int32_t value, int16_t volume) {
+    return clamp16(MixMul<int32_t, int32_t, int16_t>(value, volume) >> 12);
+}
+
+template <>
+inline int16_t MixMul<int16_t, int16_t, int32_t>(int16_t value, int32_t volume) {
+    return clamp16(MixMul<int32_t, int16_t, int32_t>(value, volume) >> 12);
+}
+
+template <>
+inline int16_t MixMul<int16_t, int32_t, int32_t>(int32_t value, int32_t volume) {
+    return clamp16(MixMul<int32_t, int32_t, int32_t>(value, volume) >> 12);
+}
+
+/*
+ * MixAccum is used to add into an accumulator register of a possibly different
+ * type. The TO and TI types are the same as MixMul.
+ */
+
+template <typename TO, typename TI>
+inline void MixAccum(TO *auxaccum, TI value) {
+    if (!is_same<TO, TI>::value) {
+        LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %d %d\n",
+                sizeof(TO), sizeof(TI));
+    }
+    *auxaccum += value;
+}
+
+template<>
+inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) {
+    static const float norm = 1. / (1 << 15);
+    *auxaccum += norm * value;
+}
+
+template<>
+inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) {
+    static const float norm = 1. / (1 << 27);
+    *auxaccum += norm * value;
+}
+
+template<>
+inline void MixAccum<int32_t, int16_t>(int32_t *auxaccum, int16_t value) {
+    *auxaccum += value << 12;
+}
+
+template<>
+inline void MixAccum<int32_t, float>(int32_t *auxaccum, float value) {
+    *auxaccum += clampq4_27_from_float(value);
+}
+
+/* MixMulAux is just like MixMul except it combines with
+ * an accumulator operation MixAccum.
+ */
+
+template <typename TO, typename TI, typename TV, typename TA>
+inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
+    MixAccum<TA, TI>(auxaccum, value);
+    return MixMul<TO, TI, TV>(value, volume);
+}
+
+/* MIXTYPE is used to determine how the samples in the input frame
+ * are mixed with volume gain into the output frame.
+ * See the volumeRampMulti functions below for more details.
+ */
+enum {
+    MIXTYPE_MULTI,
+    MIXTYPE_MONOEXPAND,
+    MIXTYPE_MULTI_SAVEONLY,
+};
+
+/*
+ * The volumeRampMulti and volumeRamp functions take a MIXTYPE
+ * which indicates the per-frame mixing and accumulation strategy.
+ *
+ * MIXTYPE_MULTI:
+ *   NCHAN represents number of input and output channels.
+ *   TO: int32_t (Q4.27) or float
+ *   TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ *   TV: int32_t (U4.28) or int16_t (U4.12) or float
+ *   vol: represents a volume array.
+ *
+ *   This accumulates into the out pointer.
+ *
+ * MIXTYPE_MONOEXPAND:
+ *   Single input channel. NCHAN represents number of output channels.
+ *   TO: int32_t (Q4.27) or float
+ *   TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ *   TV: int32_t (U4.28) or int16_t (U4.12) or float
+ *   Input channel count is 1.
+ *   vol: represents volume array.
+ *
+ *   This accumulates into the out pointer.
+ *
+ * MIXTYPE_MULTI_SAVEONLY:
+ *   NCHAN represents number of input and output channels.
+ *   TO: int16_t (Q.15) or float
+ *   TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ *   TV: int32_t (U4.28) or int16_t (U4.12) or float
+ *   vol: represents a volume array.
+ *
+ *   MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
+ */
+
+template <int MIXTYPE, int NCHAN,
+        typename TO, typename TI, typename TV, typename TA, typename TAV>
+inline void volumeRampMulti(TO* out, size_t frameCount,
+        const TI* in, TA* aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc)
+{
+#ifdef ALOGVV
+    ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE);
+#endif
+    if (aux != NULL) {
+        do {
+            TA auxaccum = 0;
+            switch (MIXTYPE) {
+            case MIXTYPE_MULTI:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+                    vol[i] += volinc[i];
+                }
+                break;
+            case MIXTYPE_MULTI_SAVEONLY:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+                    vol[i] += volinc[i];
+                }
+                break;
+            case MIXTYPE_MONOEXPAND:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
+                    vol[i] += volinc[i];
+                }
+                in++;
+                break;
+            default:
+                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+                break;
+            }
+            auxaccum /= NCHAN;
+            *aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
+            vola[0] += volainc;
+        } while (--frameCount);
+    } else {
+        do {
+            switch (MIXTYPE) {
+            case MIXTYPE_MULTI:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
+                    vol[i] += volinc[i];
+                }
+                break;
+            case MIXTYPE_MULTI_SAVEONLY:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
+                    vol[i] += volinc[i];
+                }
+                break;
+            case MIXTYPE_MONOEXPAND:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
+                    vol[i] += volinc[i];
+                }
+                in++;
+                break;
+            default:
+                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+                break;
+            }
+        } while (--frameCount);
+    }
+}
+
+template <int MIXTYPE, int NCHAN,
+        typename TO, typename TI, typename TV, typename TA, typename TAV>
+inline void volumeMulti(TO* out, size_t frameCount,
+        const TI* in, TA* aux, const TV *vol, TAV vola)
+{
+#ifdef ALOGVV
+    ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
+#endif
+    if (aux != NULL) {
+        do {
+            TA auxaccum = 0;
+            switch (MIXTYPE) {
+            case MIXTYPE_MULTI:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+                }
+                break;
+            case MIXTYPE_MULTI_SAVEONLY:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
+                }
+                break;
+            case MIXTYPE_MONOEXPAND:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
+                }
+                in++;
+                break;
+            default:
+                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+                break;
+            }
+            auxaccum /= NCHAN;
+            *aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
+        } while (--frameCount);
+    } else {
+        do {
+            switch (MIXTYPE) {
+            case MIXTYPE_MULTI:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
+                }
+                break;
+            case MIXTYPE_MULTI_SAVEONLY:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
+                }
+                break;
+            case MIXTYPE_MONOEXPAND:
+                for (int i = 0; i < NCHAN; ++i) {
+                    *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
+                }
+                in++;
+                break;
+            default:
+                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
+                break;
+            }
+        } while (--frameCount);
+    }
+}
+
+};
+
+#endif /* ANDROID_AUDIO_MIXER_OPS_H */
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 7a2a773..67a0119 100755
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1157,12 +1157,12 @@
                                              type_t type)
     :   ThreadBase(audioFlinger, id, device, AUDIO_DEVICE_NONE, type),
         mNormalFrameCount(0), mSinkBuffer(NULL),
-        mMixerBufferEnabled(false),
+        mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
         mMixerBuffer(NULL),
         mMixerBufferSize(0),
         mMixerBufferFormat(AUDIO_FORMAT_INVALID),
         mMixerBufferValid(false),
-        mEffectBufferEnabled(false),
+        mEffectBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
         mEffectBuffer(NULL),
         mEffectBufferSize(0),
         mEffectBufferFormat(AUDIO_FORMAT_INVALID),
@@ -1401,9 +1401,10 @@
                 frameCount, mFrameCount);
       } else {
         ALOGV("AUDIO_OUTPUT_FLAG_FAST denied: isTimed=%d sharedBuffer=%p frameCount=%d "
-                "mFrameCount=%d format=%d isLinear=%d channelMask=%#x sampleRate=%u mSampleRate=%u "
+                "mFrameCount=%d format=%#x mFormat=%#x isLinear=%d channelMask=%#x "
+                "sampleRate=%u mSampleRate=%u "
                 "hasFastMixer=%d tid=%d fastTrackAvailMask=%#x",
-                isTimed, sharedBuffer.get(), frameCount, mFrameCount, format,
+                isTimed, sharedBuffer.get(), frameCount, mFrameCount, format, mFormat,
                 audio_is_linear_pcm(format),
                 channelMask, sampleRate, mSampleRate, hasFastMixer(), tid, mFastTrackAvailMask);
         *flags &= ~IAudioFlinger::TRACK_FAST;
@@ -1809,9 +1810,10 @@
     if (!audio_is_valid_format(mFormat)) {
         LOG_ALWAYS_FATAL("HAL format %#x not valid for output", mFormat);
     }
-    if ((mType == MIXER || mType == DUPLICATING) && mFormat != AUDIO_FORMAT_PCM_16_BIT) {
-        LOG_ALWAYS_FATAL("HAL format %#x not supported for mixed output; "
-                "must be AUDIO_FORMAT_PCM_16_BIT", mFormat);
+    if ((mType == MIXER || mType == DUPLICATING)
+            && !isValidPcmSinkFormat(mFormat)) {
+        LOG_FATAL("HAL format %#x not supported for mixed output",
+                mFormat);
     }
     mFrameSize = audio_stream_frame_size(&mOutput->stream->common);
     mBufferSize = mOutput->stream->common.get_buffer_size(&mOutput->stream->common);
diff --git a/services/audiopolicy/Android.mk b/services/audiopolicy/Android.mk
old mode 100644
new mode 100755
index cddc503..f3be42d
--- a/services/audiopolicy/Android.mk
+++ b/services/audiopolicy/Android.mk
@@ -3,7 +3,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-    AudioPolicyService.cpp
+    AudioPolicyService.cpp \
+    AudioPolicyEffects.cpp
 
 ifeq ($(USE_LEGACY_AUDIO_POLICY), 1)
 LOCAL_SRC_FILES += \
diff --git a/services/audiopolicy/AudioPolicyEffects.cpp b/services/audiopolicy/AudioPolicyEffects.cpp
new file mode 100755
index 0000000..185e1cc
--- /dev/null
+++ b/services/audiopolicy/AudioPolicyEffects.cpp
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AudioPolicyEffects"
+#define LOG_NDEBUG 0
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <cutils/misc.h>
+#include <media/AudioEffect.h>
+#include <system/audio.h>
+#include <hardware/audio_effect.h>
+#include <audio_effects/audio_effects_conf.h>
+#include <utils/Vector.h>
+#include <utils/SortedVector.h>
+#include <cutils/config_utils.h>
+#include "AudioPolicyEffects.h"
+#include "ServiceUtilities.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+// AudioPolicyEffects Implementation
+// ----------------------------------------------------------------------------
+
+AudioPolicyEffects::AudioPolicyEffects()
+{
+    // load automatic audio effect modules
+    if (access(AUDIO_EFFECT_VENDOR_CONFIG_FILE, R_OK) == 0) {
+        loadAudioEffectConfig(AUDIO_EFFECT_VENDOR_CONFIG_FILE);
+    } else if (access(AUDIO_EFFECT_DEFAULT_CONFIG_FILE, R_OK) == 0) {
+        loadAudioEffectConfig(AUDIO_EFFECT_DEFAULT_CONFIG_FILE);
+    }
+}
+
+
+AudioPolicyEffects::~AudioPolicyEffects()
+{
+    size_t i = 0;
+    // release audio input processing resources
+    for (i = 0; i < mInputSources.size(); i++) {
+        delete mInputSources.valueAt(i);
+    }
+    mInputSources.clear();
+
+    for (i = 0; i < mInputs.size(); i++) {
+        mInputs.valueAt(i)->mEffects.clear();
+        delete mInputs.valueAt(i);
+    }
+    mInputs.clear();
+
+    // release audio output processing resources
+    for (i = 0; i < mOutputStreams.size(); i++) {
+        delete mOutputStreams.valueAt(i);
+    }
+    mOutputStreams.clear();
+
+    for (i = 0; i < mOutputSessions.size(); i++) {
+        mOutputSessions.valueAt(i)->mEffects.clear();
+        delete mOutputSessions.valueAt(i);
+    }
+    mOutputSessions.clear();
+}
+
+
+status_t AudioPolicyEffects::addInputEffects(audio_io_handle_t input,
+                             audio_source_t inputSource,
+                             int audioSession)
+{
+    status_t status = NO_ERROR;
+
+    // create audio pre processors according to input source
+    audio_source_t aliasSource = (inputSource == AUDIO_SOURCE_HOTWORD) ?
+                                    AUDIO_SOURCE_VOICE_RECOGNITION : inputSource;
+
+    ssize_t index = mInputSources.indexOfKey(aliasSource);
+    if (index < 0) {
+        ALOGV("addInputEffects(): no processing needs to be attached to this source");
+        return status;
+    }
+    ssize_t idx = mInputs.indexOfKey(input);
+    EffectVector *inputDesc;
+    if (idx < 0) {
+        inputDesc = new EffectVector(audioSession);
+        mInputs.add(input, inputDesc);
+    } else {
+        inputDesc = mInputs.valueAt(idx);
+    }
+
+    Vector <EffectDesc *> effects = mInputSources.valueAt(index)->mEffects;
+    for (size_t i = 0; i < effects.size(); i++) {
+        EffectDesc *effect = effects[i];
+        sp<AudioEffect> fx = new AudioEffect(NULL, &effect->mUuid, -1, 0, 0, audioSession, input);
+        status_t status = fx->initCheck();
+        if (status != NO_ERROR && status != ALREADY_EXISTS) {
+            ALOGW("addInputEffects(): failed to create Fx %s on source %d",
+                  effect->mName, (int32_t)aliasSource);
+            // fx goes out of scope and strong ref on AudioEffect is released
+            continue;
+        }
+        for (size_t j = 0; j < effect->mParams.size(); j++) {
+            fx->setParameter(effect->mParams[j]);
+        }
+        ALOGV("addInputEffects(): added Fx %s on source: %d", effect->mName, (int32_t)aliasSource);
+        inputDesc->mEffects.add(fx);
+    }
+    setProcessorEnabled(inputDesc, true);
+
+    return status;
+}
+
+
+status_t AudioPolicyEffects::releaseInputEffects(audio_io_handle_t input)
+{
+    status_t status = NO_ERROR;
+
+    ssize_t index = mInputs.indexOfKey(input);
+    if (index < 0) {
+        return status;
+    }
+    EffectVector *inputDesc = mInputs.valueAt(index);
+    setProcessorEnabled(inputDesc, false);
+    delete inputDesc;
+    mInputs.removeItemsAt(index);
+    ALOGV("releaseInputEffects(): all effects released");
+    return status;
+}
+
+status_t AudioPolicyEffects::queryDefaultInputEffects(int audioSession,
+                                                      effect_descriptor_t *descriptors,
+                                                      uint32_t *count)
+{
+    status_t status = NO_ERROR;
+
+    size_t index;
+    for (index = 0; index < mInputs.size(); index++) {
+        if (mInputs.valueAt(index)->mSessionId == audioSession) {
+            break;
+        }
+    }
+    if (index == mInputs.size()) {
+        *count = 0;
+        return BAD_VALUE;
+    }
+    Vector< sp<AudioEffect> > effects = mInputs.valueAt(index)->mEffects;
+
+    for (size_t i = 0; i < effects.size(); i++) {
+        effect_descriptor_t desc = effects[i]->descriptor();
+        if (i < *count) {
+            descriptors[i] = desc;
+        }
+    }
+    if (effects.size() > *count) {
+        status = NO_MEMORY;
+    }
+    *count = effects.size();
+    return status;
+}
+
+
+status_t AudioPolicyEffects::queryDefaultOutputSessionEffects(int audioSession,
+                         effect_descriptor_t *descriptors,
+                         uint32_t *count)
+{
+    status_t status = NO_ERROR;
+
+    size_t index;
+    for (index = 0; index < mOutputSessions.size(); index++) {
+        if (mOutputSessions.valueAt(index)->mSessionId == audioSession) {
+            break;
+        }
+    }
+    if (index == mOutputSessions.size()) {
+        *count = 0;
+        return BAD_VALUE;
+    }
+    Vector< sp<AudioEffect> > effects = mOutputSessions.valueAt(index)->mEffects;
+
+    for (size_t i = 0; i < effects.size(); i++) {
+        effect_descriptor_t desc = effects[i]->descriptor();
+        if (i < *count) {
+            descriptors[i] = desc;
+        }
+    }
+    if (effects.size() > *count) {
+        status = NO_MEMORY;
+    }
+    *count = effects.size();
+    return status;
+}
+
+
+status_t AudioPolicyEffects::addOutputSessionEffects(audio_io_handle_t output,
+                         audio_stream_type_t stream,
+                         int audioSession)
+{
+    status_t status = NO_ERROR;
+
+    // create audio processors according to stream
+    ssize_t index = mOutputStreams.indexOfKey(stream);
+    if (index < 0) {
+        ALOGV("addOutputSessionEffects(): no output processing needed for this stream");
+        return NO_ERROR;
+    }
+
+    ssize_t idx = mOutputSessions.indexOfKey(audioSession);
+    EffectVector *procDesc;
+    if (idx < 0) {
+        procDesc = new EffectVector(audioSession);
+        mOutputSessions.add(audioSession, procDesc);
+    } else {
+        procDesc = mOutputSessions.valueAt(idx);
+    }
+
+    Vector <EffectDesc *> effects = mOutputStreams.valueAt(index)->mEffects;
+    for (size_t i = 0; i < effects.size(); i++) {
+        EffectDesc *effect = effects[i];
+        sp<AudioEffect> fx = new AudioEffect(NULL, &effect->mUuid, 0, 0, 0, audioSession, output);
+        status_t status = fx->initCheck();
+        if (status != NO_ERROR && status != ALREADY_EXISTS) {
+            ALOGE("addOutputSessionEffects(): failed to create Fx  %s on session %d",
+                  effect->mName, audioSession);
+            // fx goes out of scope and strong ref on AudioEffect is released
+            continue;
+        }
+        ALOGV("addOutputSessionEffects(): added Fx %s on session: %d for stream: %d",
+              effect->mName, audioSession, (int32_t)stream);
+        procDesc->mEffects.add(fx);
+    }
+
+    setProcessorEnabled(procDesc, true);
+
+    return status;
+}
+
+status_t AudioPolicyEffects::releaseOutputSessionEffects(audio_io_handle_t output,
+                         audio_stream_type_t stream,
+                         int audioSession)
+{
+    status_t status = NO_ERROR;
+    (void) output; // argument not used for now
+    (void) stream; // argument not used for now
+
+    ssize_t index = mOutputSessions.indexOfKey(audioSession);
+    if (index < 0) {
+        ALOGV("releaseOutputSessionEffects: no output processing was attached to this stream");
+        return NO_ERROR;
+    }
+
+    EffectVector *procDesc = mOutputSessions.valueAt(index);
+    setProcessorEnabled(procDesc, false);
+    procDesc->mEffects.clear();
+    delete procDesc;
+    mOutputSessions.removeItemsAt(index);
+    ALOGV("releaseOutputSessionEffects(): output processing released from session: %d",
+          audioSession);
+    return status;
+}
+
+
+void AudioPolicyEffects::setProcessorEnabled(const EffectVector *effectVector, bool enabled)
+{
+    const Vector<sp<AudioEffect> > &fxVector = effectVector->mEffects;
+    for (size_t i = 0; i < fxVector.size(); i++) {
+        fxVector.itemAt(i)->setEnabled(enabled);
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+// Audio processing configuration
+// ----------------------------------------------------------------------------
+
+/*static*/ const char * const AudioPolicyEffects::kInputSourceNames[AUDIO_SOURCE_CNT -1] = {
+    MIC_SRC_TAG,
+    VOICE_UL_SRC_TAG,
+    VOICE_DL_SRC_TAG,
+    VOICE_CALL_SRC_TAG,
+    CAMCORDER_SRC_TAG,
+    VOICE_REC_SRC_TAG,
+    VOICE_COMM_SRC_TAG
+};
+
+// returns the audio_source_t enum corresponding to the input source name or
+// AUDIO_SOURCE_CNT is no match found
+audio_source_t AudioPolicyEffects::inputSourceNameToEnum(const char *name)
+{
+    int i;
+    for (i = AUDIO_SOURCE_MIC; i < AUDIO_SOURCE_CNT; i++) {
+        if (strcmp(name, kInputSourceNames[i - AUDIO_SOURCE_MIC]) == 0) {
+            ALOGV("inputSourceNameToEnum found source %s %d", name, i);
+            break;
+        }
+    }
+    return (audio_source_t)i;
+}
+
+const char *AudioPolicyEffects::kStreamNames[AUDIO_STREAM_CNT+1] = {
+    AUDIO_STREAM_DEFAULT_TAG,
+    AUDIO_STREAM_VOICE_CALL_TAG,
+    AUDIO_STREAM_SYSTEM_TAG,
+    AUDIO_STREAM_RING_TAG,
+    AUDIO_STREAM_MUSIC_TAG,
+    AUDIO_STREAM_ALARM_TAG,
+    AUDIO_STREAM_NOTIFICATION_TAG,
+    AUDIO_STREAM_BLUETOOTH_SCO_TAG,
+    AUDIO_STREAM_ENFORCED_AUDIBLE_TAG,
+    AUDIO_STREAM_DTMF_TAG,
+    AUDIO_STREAM_TTS_TAG
+};
+
+// returns the audio_stream_t enum corresponding to the output stream name or
+// AUDIO_STREAM_CNT is no match found
+audio_stream_type_t AudioPolicyEffects::streamNameToEnum(const char *name)
+{
+    int i;
+    for (i = AUDIO_STREAM_DEFAULT; i < AUDIO_STREAM_CNT; i++) {
+        if (strcmp(name, kStreamNames[i - AUDIO_STREAM_DEFAULT]) == 0) {
+            ALOGV("streamNameToEnum found stream %s %d", name, i);
+            break;
+        }
+    }
+    return (audio_stream_type_t)i;
+}
+
+// ----------------------------------------------------------------------------
+// Audio Effect Config parser
+// ----------------------------------------------------------------------------
+
+size_t AudioPolicyEffects::growParamSize(char *param,
+                                         size_t size,
+                                         size_t *curSize,
+                                         size_t *totSize)
+{
+    // *curSize is at least sizeof(effect_param_t) + 2 * sizeof(int)
+    size_t pos = ((*curSize - 1 ) / size + 1) * size;
+
+    if (pos + size > *totSize) {
+        while (pos + size > *totSize) {
+            *totSize += ((*totSize + 7) / 8) * 4;
+        }
+        param = (char *)realloc(param, *totSize);
+    }
+    *curSize = pos + size;
+    return pos;
+}
+
+size_t AudioPolicyEffects::readParamValue(cnode *node,
+                                          char *param,
+                                          size_t *curSize,
+                                          size_t *totSize)
+{
+    if (strncmp(node->name, SHORT_TAG, sizeof(SHORT_TAG) + 1) == 0) {
+        size_t pos = growParamSize(param, sizeof(short), curSize, totSize);
+        *(short *)((char *)param + pos) = (short)atoi(node->value);
+        ALOGV("readParamValue() reading short %d", *(short *)((char *)param + pos));
+        return sizeof(short);
+    } else if (strncmp(node->name, INT_TAG, sizeof(INT_TAG) + 1) == 0) {
+        size_t pos = growParamSize(param, sizeof(int), curSize, totSize);
+        *(int *)((char *)param + pos) = atoi(node->value);
+        ALOGV("readParamValue() reading int %d", *(int *)((char *)param + pos));
+        return sizeof(int);
+    } else if (strncmp(node->name, FLOAT_TAG, sizeof(FLOAT_TAG) + 1) == 0) {
+        size_t pos = growParamSize(param, sizeof(float), curSize, totSize);
+        *(float *)((char *)param + pos) = (float)atof(node->value);
+        ALOGV("readParamValue() reading float %f",*(float *)((char *)param + pos));
+        return sizeof(float);
+    } else if (strncmp(node->name, BOOL_TAG, sizeof(BOOL_TAG) + 1) == 0) {
+        size_t pos = growParamSize(param, sizeof(bool), curSize, totSize);
+        if (strncmp(node->value, "false", strlen("false") + 1) == 0) {
+            *(bool *)((char *)param + pos) = false;
+        } else {
+            *(bool *)((char *)param + pos) = true;
+        }
+        ALOGV("readParamValue() reading bool %s",*(bool *)((char *)param + pos) ? "true" : "false");
+        return sizeof(bool);
+    } else if (strncmp(node->name, STRING_TAG, sizeof(STRING_TAG) + 1) == 0) {
+        size_t len = strnlen(node->value, EFFECT_STRING_LEN_MAX);
+        if (*curSize + len + 1 > *totSize) {
+            *totSize = *curSize + len + 1;
+            param = (char *)realloc(param, *totSize);
+        }
+        strncpy(param + *curSize, node->value, len);
+        *curSize += len;
+        param[*curSize] = '\0';
+        ALOGV("readParamValue() reading string %s", param + *curSize - len);
+        return len;
+    }
+    ALOGW("readParamValue() unknown param type %s", node->name);
+    return 0;
+}
+
+effect_param_t *AudioPolicyEffects::loadEffectParameter(cnode *root)
+{
+    cnode *param;
+    cnode *value;
+    size_t curSize = sizeof(effect_param_t);
+    size_t totSize = sizeof(effect_param_t) + 2 * sizeof(int);
+    effect_param_t *fx_param = (effect_param_t *)malloc(totSize);
+
+    param = config_find(root, PARAM_TAG);
+    value = config_find(root, VALUE_TAG);
+    if (param == NULL && value == NULL) {
+        // try to parse simple parameter form {int int}
+        param = root->first_child;
+        if (param != NULL) {
+            // Note: that a pair of random strings is read as 0 0
+            int *ptr = (int *)fx_param->data;
+            int *ptr2 = (int *)((char *)param + sizeof(effect_param_t));
+            ALOGW("loadEffectParameter() ptr %p ptr2 %p", ptr, ptr2);
+            *ptr++ = atoi(param->name);
+            *ptr = atoi(param->value);
+            fx_param->psize = sizeof(int);
+            fx_param->vsize = sizeof(int);
+            return fx_param;
+        }
+    }
+    if (param == NULL || value == NULL) {
+        ALOGW("loadEffectParameter() invalid parameter description %s", root->name);
+        goto error;
+    }
+
+    fx_param->psize = 0;
+    param = param->first_child;
+    while (param) {
+        ALOGV("loadEffectParameter() reading param of type %s", param->name);
+        size_t size = readParamValue(param, (char *)fx_param, &curSize, &totSize);
+        if (size == 0) {
+            goto error;
+        }
+        fx_param->psize += size;
+        param = param->next;
+    }
+
+    // align start of value field on 32 bit boundary
+    curSize = ((curSize - 1 ) / sizeof(int) + 1) * sizeof(int);
+
+    fx_param->vsize = 0;
+    value = value->first_child;
+    while (value) {
+        ALOGV("loadEffectParameter() reading value of type %s", value->name);
+        size_t size = readParamValue(value, (char *)fx_param, &curSize, &totSize);
+        if (size == 0) {
+            goto error;
+        }
+        fx_param->vsize += size;
+        value = value->next;
+    }
+
+    return fx_param;
+
+error:
+    delete fx_param;
+    return NULL;
+}
+
+void AudioPolicyEffects::loadEffectParameters(cnode *root, Vector <effect_param_t *>& params)
+{
+    cnode *node = root->first_child;
+    while (node) {
+        ALOGV("loadEffectParameters() loading param %s", node->name);
+        effect_param_t *param = loadEffectParameter(node);
+        if (param == NULL) {
+            node = node->next;
+            continue;
+        }
+        params.add(param);
+        node = node->next;
+    }
+}
+
+
+AudioPolicyEffects::EffectDescVector *AudioPolicyEffects::loadEffectConfig(
+                                                            cnode *root,
+                                                            const Vector <EffectDesc *>& effects)
+{
+    cnode *node = root->first_child;
+    if (node == NULL) {
+        ALOGW("loadInputSource() empty element %s", root->name);
+        return NULL;
+    }
+    EffectDescVector *desc = new EffectDescVector();
+    while (node) {
+        size_t i;
+        for (i = 0; i < effects.size(); i++) {
+            if (strncmp(effects[i]->mName, node->name, EFFECT_STRING_LEN_MAX) == 0) {
+                ALOGV("loadEffectConfig() found effect %s in list", node->name);
+                break;
+            }
+        }
+        if (i == effects.size()) {
+            ALOGV("loadEffectConfig() effect %s not in list", node->name);
+            node = node->next;
+            continue;
+        }
+        EffectDesc *effect = new EffectDesc(*effects[i]);   // deep copy
+        loadEffectParameters(node, effect->mParams);
+        ALOGV("loadEffectConfig() adding effect %s uuid %08x",
+              effect->mName, effect->mUuid.timeLow);
+        desc->mEffects.add(effect);
+        node = node->next;
+    }
+    if (desc->mEffects.size() == 0) {
+        ALOGW("loadEffectConfig() no valid effects found in config %s", root->name);
+        delete desc;
+        return NULL;
+    }
+    return desc;
+}
+
+status_t AudioPolicyEffects::loadInputEffectConfigurations(cnode *root,
+                                                           const Vector <EffectDesc *>& effects)
+{
+    cnode *node = config_find(root, PREPROCESSING_TAG);
+    if (node == NULL) {
+        return -ENOENT;
+    }
+    node = node->first_child;
+    while (node) {
+        audio_source_t source = inputSourceNameToEnum(node->name);
+        if (source == AUDIO_SOURCE_CNT) {
+            ALOGW("loadInputSources() invalid input source %s", node->name);
+            node = node->next;
+            continue;
+        }
+        ALOGV("loadInputSources() loading input source %s", node->name);
+        EffectDescVector *desc = loadEffectConfig(node, effects);
+        if (desc == NULL) {
+            node = node->next;
+            continue;
+        }
+        mInputSources.add(source, desc);
+        node = node->next;
+    }
+    return NO_ERROR;
+}
+
+status_t AudioPolicyEffects::loadStreamEffectConfigurations(cnode *root,
+                                                            const Vector <EffectDesc *>& effects)
+{
+    cnode *node = config_find(root, OUTPUT_SESSION_PROCESSING_TAG);
+    if (node == NULL) {
+        return -ENOENT;
+    }
+    node = node->first_child;
+    while (node) {
+        audio_stream_type_t stream = streamNameToEnum(node->name);
+        if (stream == AUDIO_STREAM_CNT) {
+            ALOGW("loadStreamEffectConfigurations() invalid output stream %s", node->name);
+            node = node->next;
+            continue;
+        }
+        ALOGV("loadStreamEffectConfigurations() loading output stream %s", node->name);
+        EffectDescVector *desc = loadEffectConfig(node, effects);
+        if (desc == NULL) {
+            node = node->next;
+            continue;
+        }
+        mOutputStreams.add(stream, desc);
+        node = node->next;
+    }
+    return NO_ERROR;
+}
+
+AudioPolicyEffects::EffectDesc *AudioPolicyEffects::loadEffect(cnode *root)
+{
+    cnode *node = config_find(root, UUID_TAG);
+    if (node == NULL) {
+        return NULL;
+    }
+    effect_uuid_t uuid;
+    if (AudioEffect::stringToGuid(node->value, &uuid) != NO_ERROR) {
+        ALOGW("loadEffect() invalid uuid %s", node->value);
+        return NULL;
+    }
+    return new EffectDesc(root->name, uuid);
+}
+
+status_t AudioPolicyEffects::loadEffects(cnode *root, Vector <EffectDesc *>& effects)
+{
+    cnode *node = config_find(root, EFFECTS_TAG);
+    if (node == NULL) {
+        return -ENOENT;
+    }
+    node = node->first_child;
+    while (node) {
+        ALOGV("loadEffects() loading effect %s", node->name);
+        EffectDesc *effect = loadEffect(node);
+        if (effect == NULL) {
+            node = node->next;
+            continue;
+        }
+        effects.add(effect);
+        node = node->next;
+    }
+    return NO_ERROR;
+}
+
+status_t AudioPolicyEffects::loadAudioEffectConfig(const char *path)
+{
+    cnode *root;
+    char *data;
+
+    data = (char *)load_file(path, NULL);
+    if (data == NULL) {
+        return -ENODEV;
+    }
+    root = config_node("", "");
+    config_load(root, data);
+
+    Vector <EffectDesc *> effects;
+    loadEffects(root, effects);
+    loadInputEffectConfigurations(root, effects);
+    loadStreamEffectConfigurations(root, effects);
+
+    config_free(root);
+    free(root);
+    free(data);
+
+    return NO_ERROR;
+}
+
+
+}; // namespace android
diff --git a/services/audiopolicy/AudioPolicyEffects.h b/services/audiopolicy/AudioPolicyEffects.h
new file mode 100755
index 0000000..351cb1a
--- /dev/null
+++ b/services/audiopolicy/AudioPolicyEffects.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIOPOLICYEFFECTS_H
+#define ANDROID_AUDIOPOLICYEFFECTS_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <cutils/misc.h>
+#include <media/AudioEffect.h>
+#include <system/audio.h>
+#include <hardware/audio_effect.h>
+#include <utils/Vector.h>
+#include <utils/SortedVector.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+// AudioPolicyEffects class
+// This class will manage all effects attached to input and output streams in
+// AudioPolicyService as configured in audio_effects.conf.
+class AudioPolicyEffects : public RefBase
+{
+
+public:
+
+    // The constructor will parse audio_effects.conf
+    // First it will look whether vendor specific file exists,
+    // otherwise it will parse the system default file.
+	         AudioPolicyEffects();
+    virtual ~AudioPolicyEffects();
+
+    // Return a list of effect descriptors for default input effects
+    // associated with audioSession
+    status_t queryDefaultInputEffects(int audioSession,
+                             effect_descriptor_t *descriptors,
+                             uint32_t *count);
+
+    // Add all input effects associated with this input
+    // Effects are attached depending on the audio_source_t
+    status_t addInputEffects(audio_io_handle_t input,
+                             audio_source_t inputSource,
+                             int audioSession);
+
+    // Add all input effects associated to this input
+    status_t releaseInputEffects(audio_io_handle_t input);
+
+
+    // Return a list of effect descriptors for default output effects
+    // associated with audioSession
+    status_t queryDefaultOutputSessionEffects(int audioSession,
+                             effect_descriptor_t *descriptors,
+                             uint32_t *count);
+
+    // Add all output effects associated to this output
+    // Effects are attached depending on the audio_stream_type_t
+    status_t addOutputSessionEffects(audio_io_handle_t output,
+                             audio_stream_type_t stream,
+                             int audioSession);
+
+    // release all output effects associated with this output stream and audiosession
+    status_t releaseOutputSessionEffects(audio_io_handle_t output,
+                             audio_stream_type_t stream,
+                             int audioSession);
+
+private:
+
+    // class to store the description of an effects and its parameters
+    // as defined in audio_effects.conf
+    class EffectDesc {
+    public:
+        EffectDesc(const char *name, const effect_uuid_t& uuid) :
+                        mName(strdup(name)),
+                        mUuid(uuid) { }
+        EffectDesc(const EffectDesc& orig) :
+                        mName(strdup(orig.mName)),
+                        mUuid(orig.mUuid) {
+                            // deep copy mParams
+                            for (size_t k = 0; k < orig.mParams.size(); k++) {
+                                effect_param_t *origParam = orig.mParams[k];
+                                // psize and vsize are rounded up to an int boundary for allocation
+                                size_t origSize = sizeof(effect_param_t) +
+                                                  ((origParam->psize + 3) & ~3) +
+                                                  ((origParam->vsize + 3) & ~3);
+                                effect_param_t *dupParam = (effect_param_t *) malloc(origSize);
+                                memcpy(dupParam, origParam, origSize);
+                                // This works because the param buffer allocation is also done by
+                                // multiples of 4 bytes originally. In theory we should memcpy only
+                                // the actual param size, that is without rounding vsize.
+                                mParams.add(dupParam);
+                            }
+                        }
+        /*virtual*/ ~EffectDesc() {
+            free(mName);
+            for (size_t k = 0; k < mParams.size(); k++) {
+                free(mParams[k]);
+            }
+        }
+        char *mName;
+        effect_uuid_t mUuid;
+        Vector <effect_param_t *> mParams;
+    };
+
+    // class to store voctor of EffectDesc
+    class EffectDescVector {
+    public:
+        EffectDescVector() {}
+        /*virtual*/ ~EffectDescVector() {
+            for (size_t j = 0; j < mEffects.size(); j++) {
+                delete mEffects[j];
+            }
+        }
+        Vector <EffectDesc *> mEffects;
+    };
+
+    // class to store voctor of AudioEffects
+    class EffectVector {
+    public:
+        EffectVector(int session) : mSessionId(session) {}
+        /*virtual*/ ~EffectVector() {}
+        const int mSessionId;
+        Vector< sp<AudioEffect> >mEffects;
+    };
+
+
+    static const char * const kInputSourceNames[AUDIO_SOURCE_CNT -1];
+    audio_source_t inputSourceNameToEnum(const char *name);
+
+    static const char *kStreamNames[AUDIO_STREAM_CNT+1]; //+1 required as streams start from -1
+    audio_stream_type_t streamNameToEnum(const char *name);
+
+    // Enable or disable all effects in effect vector
+    void setProcessorEnabled(const EffectVector *effectVector, bool enabled);
+
+    // Parse audio_effects.conf
+    status_t loadAudioEffectConfig(const char *path);
+
+    // Load all effects descriptors in configuration file
+    status_t loadEffects(cnode *root, Vector <EffectDesc *>& effects);
+    EffectDesc *loadEffect(cnode *root);
+
+    // Load all automatic effect configurations
+    status_t loadInputEffectConfigurations(cnode *root, const Vector <EffectDesc *>& effects);
+    status_t loadStreamEffectConfigurations(cnode *root, const Vector <EffectDesc *>& effects);
+    EffectDescVector *loadEffectConfig(cnode *root, const Vector <EffectDesc *>& effects);
+
+    // Load all automatic effect parameters
+    void loadEffectParameters(cnode *root, Vector <effect_param_t *>& params);
+    effect_param_t *loadEffectParameter(cnode *root);
+    size_t readParamValue(cnode *node,
+                          char *param,
+                          size_t *curSize,
+                          size_t *totSize);
+    size_t growParamSize(char *param,
+                         size_t size,
+                         size_t *curSize,
+                         size_t *totSize);
+
+    // Automatic input effects are configured per audio_source_t
+    KeyedVector< audio_source_t, EffectDescVector* > mInputSources;
+    // Automatic input effects are unique for audio_io_handle_t
+    KeyedVector< audio_io_handle_t, EffectVector* > mInputs;
+
+    // Automatic output effects are organized per audio_stream_type_t
+    KeyedVector< audio_stream_type_t, EffectDescVector* > mOutputStreams;
+    // Automatic output effects are unique for audiosession ID
+    KeyedVector< int32_t, EffectVector* > mOutputSessions;
+};
+
+}; // namespace android
+
+#endif // ANDROID_AUDIOPOLICYEFFECTS_H
diff --git a/services/audiopolicy/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/AudioPolicyInterfaceImpl.cpp
old mode 100644
new mode 100755
index 6342d8f..5a13ac2
--- a/services/audiopolicy/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/AudioPolicyInterfaceImpl.cpp
@@ -156,6 +156,13 @@
     }
     ALOGV("startOutput()");
     Mutex::Autolock _l(mLock);
+
+    // create audio processors according to stream
+    status_t status = mAudioPolicyEffects->addOutputSessionEffects(output, stream, session);
+    if (status != NO_ERROR && status != ALREADY_EXISTS) {
+        ALOGW("Failed to add effects on session %d", session);
+    }
+
     return mAudioPolicyManager->startOutput(output, stream, session);
 }
 
@@ -177,6 +184,13 @@
 {
     ALOGV("doStopOutput from tid %d", gettid());
     Mutex::Autolock _l(mLock);
+
+    // release audio processors from the stream
+    status_t status = mAudioPolicyEffects->releaseOutputSessionEffects(output, stream, session);
+    if (status != NO_ERROR && status != ALREADY_EXISTS) {
+        ALOGW("Failed to release effects on session %d", session);
+    }
+
     return mAudioPolicyManager->stopOutput(output, stream, session);
 }
 
@@ -222,39 +236,13 @@
     if (input == 0) {
         return input;
     }
+
     // create audio pre processors according to input source
-    audio_source_t aliasSource = (inputSource == AUDIO_SOURCE_HOTWORD) ?
-                                    AUDIO_SOURCE_VOICE_RECOGNITION : inputSource;
-
-    ssize_t index = mInputSources.indexOfKey(aliasSource);
-    if (index < 0) {
-        return input;
-    }
-    ssize_t idx = mInputs.indexOfKey(input);
-    InputDesc *inputDesc;
-    if (idx < 0) {
-        inputDesc = new InputDesc(audioSession);
-        mInputs.add(input, inputDesc);
-    } else {
-        inputDesc = mInputs.valueAt(idx);
+    status_t status = mAudioPolicyEffects->addInputEffects(input, inputSource, audioSession);
+    if (status != NO_ERROR && status != ALREADY_EXISTS) {
+        ALOGW("Failed to add effects on input %d", input);
     }
 
-    Vector <EffectDesc *> effects = mInputSources.valueAt(index)->mEffects;
-    for (size_t i = 0; i < effects.size(); i++) {
-        EffectDesc *effect = effects[i];
-        sp<AudioEffect> fx = new AudioEffect(NULL, &effect->mUuid, -1, 0, 0, audioSession, input);
-        status_t status = fx->initCheck();
-        if (status != NO_ERROR && status != ALREADY_EXISTS) {
-            ALOGW("Failed to create Fx %s on input %d", effect->mName, input);
-            // fx goes out of scope and strong ref on AudioEffect is released
-            continue;
-        }
-        for (size_t j = 0; j < effect->mParams.size(); j++) {
-            fx->setParameter(effect->mParams[j]);
-        }
-        inputDesc->mEffects.add(fx);
-    }
-    setPreProcessorEnabled(inputDesc, true);
     return input;
 }
 
@@ -286,14 +274,11 @@
     Mutex::Autolock _l(mLock);
     mAudioPolicyManager->releaseInput(input);
 
-    ssize_t index = mInputs.indexOfKey(input);
-    if (index < 0) {
-        return;
+    // release audio processors from the input
+    status_t status = mAudioPolicyEffects->releaseInputEffects(input);
+    if(status != NO_ERROR) {
+        ALOGW("Failed to release effects on input %d", input);
     }
-    InputDesc *inputDesc = mInputs.valueAt(index);
-    setPreProcessorEnabled(inputDesc, false);
-    delete inputDesc;
-    mInputs.removeItemsAt(index);
 }
 
 status_t AudioPolicyService::initStreamVolume(audio_stream_type_t stream,
@@ -436,37 +421,13 @@
                                                        effect_descriptor_t *descriptors,
                                                        uint32_t *count)
 {
-
     if (mAudioPolicyManager == NULL) {
         *count = 0;
         return NO_INIT;
     }
     Mutex::Autolock _l(mLock);
-    status_t status = NO_ERROR;
 
-    size_t index;
-    for (index = 0; index < mInputs.size(); index++) {
-        if (mInputs.valueAt(index)->mSessionId == audioSession) {
-            break;
-        }
-    }
-    if (index == mInputs.size()) {
-        *count = 0;
-        return BAD_VALUE;
-    }
-    Vector< sp<AudioEffect> > effects = mInputs.valueAt(index)->mEffects;
-
-    for (size_t i = 0; i < effects.size(); i++) {
-        effect_descriptor_t desc = effects[i]->descriptor();
-        if (i < *count) {
-            descriptors[i] = desc;
-        }
-    }
-    if (effects.size() > *count) {
-        status = NO_MEMORY;
-    }
-    *count = effects.size();
-    return status;
+    return mAudioPolicyEffects->queryDefaultInputEffects(audioSession, descriptors, count);
 }
 
 bool AudioPolicyService::isOffloadSupported(const audio_offload_info_t& info)
diff --git a/services/audiopolicy/AudioPolicyInterfaceImplLegacy.cpp b/services/audiopolicy/AudioPolicyInterfaceImplLegacy.cpp
old mode 100644
new mode 100755
index 0bf4982..406988c
--- a/services/audiopolicy/AudioPolicyInterfaceImplLegacy.cpp
+++ b/services/audiopolicy/AudioPolicyInterfaceImplLegacy.cpp
@@ -144,6 +144,13 @@
     }
     ALOGV("startOutput()");
     Mutex::Autolock _l(mLock);
+
+    // create audio processors according to stream
+    status_t status = mAudioPolicyEffects->addOutputSessionEffects(output, stream, session);
+    if (status != NO_ERROR && status != ALREADY_EXISTS) {
+        ALOGW("Failed to add effects on session %d", session);
+    }
+
     return mpAudioPolicy->start_output(mpAudioPolicy, output, stream, session);
 }
 
@@ -165,6 +172,13 @@
 {
     ALOGV("doStopOutput from tid %d", gettid());
     Mutex::Autolock _l(mLock);
+
+    // release audio processors from the stream
+    status_t status = mAudioPolicyEffects->releaseOutputSessionEffects(output, stream, session);
+    if (status != NO_ERROR && status != ALREADY_EXISTS) {
+        ALOGW("Failed to release effects on session %d", session);
+    }
+
     return mpAudioPolicy->stop_output(mpAudioPolicy, output, stream, session);
 }
 
@@ -210,39 +224,13 @@
     if (input == 0) {
         return input;
     }
+
     // create audio pre processors according to input source
-    audio_source_t aliasSource = (inputSource == AUDIO_SOURCE_HOTWORD) ?
-                                    AUDIO_SOURCE_VOICE_RECOGNITION : inputSource;
-
-    ssize_t index = mInputSources.indexOfKey(aliasSource);
-    if (index < 0) {
-        return input;
-    }
-    ssize_t idx = mInputs.indexOfKey(input);
-    InputDesc *inputDesc;
-    if (idx < 0) {
-        inputDesc = new InputDesc(audioSession);
-        mInputs.add(input, inputDesc);
-    } else {
-        inputDesc = mInputs.valueAt(idx);
+    status_t status = mAudioPolicyEffects->addInputEffects(input, inputSource, audioSession);
+    if (status != NO_ERROR && status != ALREADY_EXISTS) {
+        ALOGW("Failed to add effects on input %d", input);
     }
 
-    Vector <EffectDesc *> effects = mInputSources.valueAt(index)->mEffects;
-    for (size_t i = 0; i < effects.size(); i++) {
-        EffectDesc *effect = effects[i];
-        sp<AudioEffect> fx = new AudioEffect(NULL, &effect->mUuid, -1, 0, 0, audioSession, input);
-        status_t status = fx->initCheck();
-        if (status != NO_ERROR && status != ALREADY_EXISTS) {
-            ALOGW("Failed to create Fx %s on input %d", effect->mName, input);
-            // fx goes out of scope and strong ref on AudioEffect is released
-            continue;
-        }
-        for (size_t j = 0; j < effect->mParams.size(); j++) {
-            fx->setParameter(effect->mParams[j]);
-        }
-        inputDesc->mEffects.add(fx);
-    }
-    setPreProcessorEnabled(inputDesc, true);
     return input;
 }
 
@@ -274,14 +262,11 @@
     Mutex::Autolock _l(mLock);
     mpAudioPolicy->release_input(mpAudioPolicy, input);
 
-    ssize_t index = mInputs.indexOfKey(input);
-    if (index < 0) {
-        return;
+    // release audio processors from the input
+    status_t status = mAudioPolicyEffects->releaseInputEffects(input);
+    if(status != NO_ERROR) {
+        ALOGW("Failed to release effects on input %d", input);
     }
-    InputDesc *inputDesc = mInputs.valueAt(index);
-    setPreProcessorEnabled(inputDesc, false);
-    delete inputDesc;
-    mInputs.removeItemsAt(index);
 }
 
 status_t AudioPolicyService::initStreamVolume(audio_stream_type_t stream,
@@ -437,37 +422,13 @@
                                                        effect_descriptor_t *descriptors,
                                                        uint32_t *count)
 {
-
     if (mpAudioPolicy == NULL) {
         *count = 0;
         return NO_INIT;
     }
     Mutex::Autolock _l(mLock);
-    status_t status = NO_ERROR;
 
-    size_t index;
-    for (index = 0; index < mInputs.size(); index++) {
-        if (mInputs.valueAt(index)->mSessionId == audioSession) {
-            break;
-        }
-    }
-    if (index == mInputs.size()) {
-        *count = 0;
-        return BAD_VALUE;
-    }
-    Vector< sp<AudioEffect> > effects = mInputs.valueAt(index)->mEffects;
-
-    for (size_t i = 0; i < effects.size(); i++) {
-        effect_descriptor_t desc = effects[i]->descriptor();
-        if (i < *count) {
-            descriptors[i] = desc;
-        }
-    }
-    if (effects.size() > *count) {
-        status = NO_MEMORY;
-    }
-    *count = effects.size();
-    return status;
+    return mAudioPolicyEffects->queryDefaultInputEffects(audioSession, descriptors, count);
 }
 
 bool AudioPolicyService::isOffloadSupported(const audio_offload_info_t& info)
diff --git a/services/audiopolicy/AudioPolicyService.cpp b/services/audiopolicy/AudioPolicyService.cpp
old mode 100644
new mode 100755
index 93fab11..9435797
--- a/services/audiopolicy/AudioPolicyService.cpp
+++ b/services/audiopolicy/AudioPolicyService.cpp
@@ -40,8 +40,6 @@
 #include <system/audio.h>
 #include <system/audio_policy.h>
 #include <hardware/audio_policy.h>
-#include <audio_effects/audio_effects_conf.h>
-#include <media/AudioParameter.h>
 
 namespace android {
 
@@ -111,12 +109,8 @@
     mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);
 #endif
 
-    // load audio pre processing modules
-    if (access(AUDIO_EFFECT_VENDOR_CONFIG_FILE, R_OK) == 0) {
-        loadPreProcessorConfig(AUDIO_EFFECT_VENDOR_CONFIG_FILE);
-    } else if (access(AUDIO_EFFECT_DEFAULT_CONFIG_FILE, R_OK) == 0) {
-        loadPreProcessorConfig(AUDIO_EFFECT_DEFAULT_CONFIG_FILE);
-    }
+    // load audio processing modules
+    mAudioPolicyEffects = new AudioPolicyEffects();
 }
 
 AudioPolicyService::~AudioPolicyService()
@@ -125,18 +119,6 @@
     mAudioCommandThread->exit();
     mOutputCommandThread->exit();
 
-    // release audio pre processing resources
-    for (size_t i = 0; i < mInputSources.size(); i++) {
-        delete mInputSources.valueAt(i);
-    }
-    mInputSources.clear();
-
-    for (size_t i = 0; i < mInputs.size(); i++) {
-        mInputs.valueAt(i)->mEffects.clear();
-        delete mInputs.valueAt(i);
-    }
-    mInputs.clear();
-
 #ifdef USE_LEGACY_AUDIO_POLICY
     if (mpAudioPolicy != NULL && mpAudioPolicyDev != NULL) {
         mpAudioPolicyDev->destroy_audio_policy(mpAudioPolicyDev, mpAudioPolicy);
@@ -150,6 +132,7 @@
 #endif
 
     mNotificationClients.clear();
+    mAudioPolicyEffects.clear();
 }
 
 // A notification client is always registered by AudioSystem when the client process
@@ -353,14 +336,6 @@
     return NO_ERROR;
 }
 
-void AudioPolicyService::setPreProcessorEnabled(const InputDesc *inputDesc, bool enabled)
-{
-    const Vector<sp<AudioEffect> > &fxVector = inputDesc->mEffects;
-    for (size_t i = 0; i < fxVector.size(); i++) {
-        fxVector.itemAt(i)->setEnabled(enabled);
-    }
-}
-
 status_t AudioPolicyService::onTransact(
         uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
 {
@@ -936,304 +911,6 @@
     return (int)mAudioCommandThread->voiceVolumeCommand(volume, delayMs);
 }
 
-// ----------------------------------------------------------------------------
-// Audio pre-processing configuration
-// ----------------------------------------------------------------------------
-
-/*static*/ const char * const AudioPolicyService::kInputSourceNames[AUDIO_SOURCE_CNT -1] = {
-    MIC_SRC_TAG,
-    VOICE_UL_SRC_TAG,
-    VOICE_DL_SRC_TAG,
-    VOICE_CALL_SRC_TAG,
-    CAMCORDER_SRC_TAG,
-    VOICE_REC_SRC_TAG,
-    VOICE_COMM_SRC_TAG
-};
-
-// returns the audio_source_t enum corresponding to the input source name or
-// AUDIO_SOURCE_CNT is no match found
-audio_source_t AudioPolicyService::inputSourceNameToEnum(const char *name)
-{
-    int i;
-    for (i = AUDIO_SOURCE_MIC; i < AUDIO_SOURCE_CNT; i++) {
-        if (strcmp(name, kInputSourceNames[i - AUDIO_SOURCE_MIC]) == 0) {
-            ALOGV("inputSourceNameToEnum found source %s %d", name, i);
-            break;
-        }
-    }
-    return (audio_source_t)i;
-}
-
-size_t AudioPolicyService::growParamSize(char *param,
-                                         size_t size,
-                                         size_t *curSize,
-                                         size_t *totSize)
-{
-    // *curSize is at least sizeof(effect_param_t) + 2 * sizeof(int)
-    size_t pos = ((*curSize - 1 ) / size + 1) * size;
-
-    if (pos + size > *totSize) {
-        while (pos + size > *totSize) {
-            *totSize += ((*totSize + 7) / 8) * 4;
-        }
-        param = (char *)realloc(param, *totSize);
-    }
-    *curSize = pos + size;
-    return pos;
-}
-
-size_t AudioPolicyService::readParamValue(cnode *node,
-                                          char *param,
-                                          size_t *curSize,
-                                          size_t *totSize)
-{
-    if (strncmp(node->name, SHORT_TAG, sizeof(SHORT_TAG) + 1) == 0) {
-        size_t pos = growParamSize(param, sizeof(short), curSize, totSize);
-        *(short *)((char *)param + pos) = (short)atoi(node->value);
-        ALOGV("readParamValue() reading short %d", *(short *)((char *)param + pos));
-        return sizeof(short);
-    } else if (strncmp(node->name, INT_TAG, sizeof(INT_TAG) + 1) == 0) {
-        size_t pos = growParamSize(param, sizeof(int), curSize, totSize);
-        *(int *)((char *)param + pos) = atoi(node->value);
-        ALOGV("readParamValue() reading int %d", *(int *)((char *)param + pos));
-        return sizeof(int);
-    } else if (strncmp(node->name, FLOAT_TAG, sizeof(FLOAT_TAG) + 1) == 0) {
-        size_t pos = growParamSize(param, sizeof(float), curSize, totSize);
-        *(float *)((char *)param + pos) = (float)atof(node->value);
-        ALOGV("readParamValue() reading float %f",*(float *)((char *)param + pos));
-        return sizeof(float);
-    } else if (strncmp(node->name, BOOL_TAG, sizeof(BOOL_TAG) + 1) == 0) {
-        size_t pos = growParamSize(param, sizeof(bool), curSize, totSize);
-        if (strncmp(node->value, "false", strlen("false") + 1) == 0) {
-            *(bool *)((char *)param + pos) = false;
-        } else {
-            *(bool *)((char *)param + pos) = true;
-        }
-        ALOGV("readParamValue() reading bool %s",*(bool *)((char *)param + pos) ? "true" : "false");
-        return sizeof(bool);
-    } else if (strncmp(node->name, STRING_TAG, sizeof(STRING_TAG) + 1) == 0) {
-        size_t len = strnlen(node->value, EFFECT_STRING_LEN_MAX);
-        if (*curSize + len + 1 > *totSize) {
-            *totSize = *curSize + len + 1;
-            param = (char *)realloc(param, *totSize);
-        }
-        strncpy(param + *curSize, node->value, len);
-        *curSize += len;
-        param[*curSize] = '\0';
-        ALOGV("readParamValue() reading string %s", param + *curSize - len);
-        return len;
-    }
-    ALOGW("readParamValue() unknown param type %s", node->name);
-    return 0;
-}
-
-effect_param_t *AudioPolicyService::loadEffectParameter(cnode *root)
-{
-    cnode *param;
-    cnode *value;
-    size_t curSize = sizeof(effect_param_t);
-    size_t totSize = sizeof(effect_param_t) + 2 * sizeof(int);
-    effect_param_t *fx_param = (effect_param_t *)malloc(totSize);
-
-    param = config_find(root, PARAM_TAG);
-    value = config_find(root, VALUE_TAG);
-    if (param == NULL && value == NULL) {
-        // try to parse simple parameter form {int int}
-        param = root->first_child;
-        if (param != NULL) {
-            // Note: that a pair of random strings is read as 0 0
-            int *ptr = (int *)fx_param->data;
-            int *ptr2 = (int *)((char *)param + sizeof(effect_param_t));
-            ALOGW("loadEffectParameter() ptr %p ptr2 %p", ptr, ptr2);
-            *ptr++ = atoi(param->name);
-            *ptr = atoi(param->value);
-            fx_param->psize = sizeof(int);
-            fx_param->vsize = sizeof(int);
-            return fx_param;
-        }
-    }
-    if (param == NULL || value == NULL) {
-        ALOGW("loadEffectParameter() invalid parameter description %s", root->name);
-        goto error;
-    }
-
-    fx_param->psize = 0;
-    param = param->first_child;
-    while (param) {
-        ALOGV("loadEffectParameter() reading param of type %s", param->name);
-        size_t size = readParamValue(param, (char *)fx_param, &curSize, &totSize);
-        if (size == 0) {
-            goto error;
-        }
-        fx_param->psize += size;
-        param = param->next;
-    }
-
-    // align start of value field on 32 bit boundary
-    curSize = ((curSize - 1 ) / sizeof(int) + 1) * sizeof(int);
-
-    fx_param->vsize = 0;
-    value = value->first_child;
-    while (value) {
-        ALOGV("loadEffectParameter() reading value of type %s", value->name);
-        size_t size = readParamValue(value, (char *)fx_param, &curSize, &totSize);
-        if (size == 0) {
-            goto error;
-        }
-        fx_param->vsize += size;
-        value = value->next;
-    }
-
-    return fx_param;
-
-error:
-    free(fx_param);
-    return NULL;
-}
-
-void AudioPolicyService::loadEffectParameters(cnode *root, Vector <effect_param_t *>& params)
-{
-    cnode *node = root->first_child;
-    while (node) {
-        ALOGV("loadEffectParameters() loading param %s", node->name);
-        effect_param_t *param = loadEffectParameter(node);
-        if (param == NULL) {
-            node = node->next;
-            continue;
-        }
-        params.add(param);
-        node = node->next;
-    }
-}
-
-AudioPolicyService::InputSourceDesc *AudioPolicyService::loadInputSource(
-                                                            cnode *root,
-                                                            const Vector <EffectDesc *>& effects)
-{
-    cnode *node = root->first_child;
-    if (node == NULL) {
-        ALOGW("loadInputSource() empty element %s", root->name);
-        return NULL;
-    }
-    InputSourceDesc *source = new InputSourceDesc();
-    while (node) {
-        size_t i;
-        for (i = 0; i < effects.size(); i++) {
-            if (strncmp(effects[i]->mName, node->name, EFFECT_STRING_LEN_MAX) == 0) {
-                ALOGV("loadInputSource() found effect %s in list", node->name);
-                break;
-            }
-        }
-        if (i == effects.size()) {
-            ALOGV("loadInputSource() effect %s not in list", node->name);
-            node = node->next;
-            continue;
-        }
-        EffectDesc *effect = new EffectDesc(*effects[i]);   // deep copy
-        loadEffectParameters(node, effect->mParams);
-        ALOGV("loadInputSource() adding effect %s uuid %08x", effect->mName, effect->mUuid.timeLow);
-        source->mEffects.add(effect);
-        node = node->next;
-    }
-    if (source->mEffects.size() == 0) {
-        ALOGW("loadInputSource() no valid effects found in source %s", root->name);
-        delete source;
-        return NULL;
-    }
-    return source;
-}
-
-status_t AudioPolicyService::loadInputSources(cnode *root, const Vector <EffectDesc *>& effects)
-{
-    cnode *node = config_find(root, PREPROCESSING_TAG);
-    if (node == NULL) {
-        return -ENOENT;
-    }
-    node = node->first_child;
-    while (node) {
-        audio_source_t source = inputSourceNameToEnum(node->name);
-        if (source == AUDIO_SOURCE_CNT) {
-            ALOGW("loadInputSources() invalid input source %s", node->name);
-            node = node->next;
-            continue;
-        }
-        ALOGV("loadInputSources() loading input source %s", node->name);
-        InputSourceDesc *desc = loadInputSource(node, effects);
-        if (desc == NULL) {
-            node = node->next;
-            continue;
-        }
-        mInputSources.add(source, desc);
-        node = node->next;
-    }
-    return NO_ERROR;
-}
-
-AudioPolicyService::EffectDesc *AudioPolicyService::loadEffect(cnode *root)
-{
-    cnode *node = config_find(root, UUID_TAG);
-    if (node == NULL) {
-        return NULL;
-    }
-    effect_uuid_t uuid;
-    if (AudioEffect::stringToGuid(node->value, &uuid) != NO_ERROR) {
-        ALOGW("loadEffect() invalid uuid %s", node->value);
-        return NULL;
-    }
-    return new EffectDesc(root->name, uuid);
-}
-
-status_t AudioPolicyService::loadEffects(cnode *root, Vector <EffectDesc *>& effects)
-{
-    cnode *node = config_find(root, EFFECTS_TAG);
-    if (node == NULL) {
-        return -ENOENT;
-    }
-    node = node->first_child;
-    while (node) {
-        ALOGV("loadEffects() loading effect %s", node->name);
-        EffectDesc *effect = loadEffect(node);
-        if (effect == NULL) {
-            node = node->next;
-            continue;
-        }
-        effects.add(effect);
-        node = node->next;
-    }
-    return NO_ERROR;
-}
-
-status_t AudioPolicyService::loadPreProcessorConfig(const char *path)
-{
-    cnode *root;
-    char *data;
-
-    data = (char *)load_file(path, NULL);
-    if (data == NULL) {
-        return -ENODEV;
-    }
-    root = config_node("", "");
-    config_load(root, data);
-
-    Vector <EffectDesc *> effects;
-    loadEffects(root, effects);
-    loadInputSources(root, effects);
-
-    // delete effects to fix memory leak.
-    // as effects is local var and valgrind would treat this as memory leak
-    // and although it only did in mediaserver init, but free it in case mediaserver reboot
-    size_t i;
-    for (i = 0; i < effects.size(); i++) {
-      delete effects[i];
-    }
-
-    config_free(root);
-    free(root);
-    free(data);
-
-    return NO_ERROR;
-}
-
 extern "C" {
 audio_module_handle_t aps_load_hw_module(void *service __unused,
                                              const char *name);
diff --git a/services/audiopolicy/AudioPolicyService.h b/services/audiopolicy/AudioPolicyService.h
old mode 100644
new mode 100755
index 69673cd..380fd5e
--- a/services/audiopolicy/AudioPolicyService.h
+++ b/services/audiopolicy/AudioPolicyService.h
@@ -31,8 +31,10 @@
 #include <media/ToneGenerator.h>
 #include <media/AudioEffect.h>
 #include <hardware_legacy/AudioPolicyInterface.h>
+#include "AudioPolicyEffects.h"
 #include "AudioPolicyManager.h"
 
+
 namespace android {
 
 // ----------------------------------------------------------------------------
@@ -337,60 +339,6 @@
         wp<AudioPolicyService> mService;
     };
 
-    class EffectDesc {
-    public:
-        EffectDesc(const char *name, const effect_uuid_t& uuid) :
-                        mName(strdup(name)),
-                        mUuid(uuid) { }
-        EffectDesc(const EffectDesc& orig) :
-                        mName(strdup(orig.mName)),
-                        mUuid(orig.mUuid) {
-                            // deep copy mParams
-                            for (size_t k = 0; k < orig.mParams.size(); k++) {
-                                effect_param_t *origParam = orig.mParams[k];
-                                // psize and vsize are rounded up to an int boundary for allocation
-                                size_t origSize = sizeof(effect_param_t) +
-                                                  ((origParam->psize + 3) & ~3) +
-                                                  ((origParam->vsize + 3) & ~3);
-                                effect_param_t *dupParam = (effect_param_t *) malloc(origSize);
-                                memcpy(dupParam, origParam, origSize);
-                                // This works because the param buffer allocation is also done by
-                                // multiples of 4 bytes originally. In theory we should memcpy only
-                                // the actual param size, that is without rounding vsize.
-                                mParams.add(dupParam);
-                            }
-                        }
-        /*virtual*/ ~EffectDesc() {
-            free(mName);
-            for (size_t k = 0; k < mParams.size(); k++) {
-                free(mParams[k]);
-            }
-        }
-        char *mName;
-        effect_uuid_t mUuid;
-        Vector <effect_param_t *> mParams;
-    };
-
-    class InputSourceDesc {
-    public:
-        InputSourceDesc() {}
-        /*virtual*/ ~InputSourceDesc() {
-            for (size_t j = 0; j < mEffects.size(); j++) {
-                delete mEffects[j];
-            }
-        }
-        Vector <EffectDesc *> mEffects;
-    };
-
-
-    class InputDesc {
-    public:
-        InputDesc(int session) : mSessionId(session) {}
-        /*virtual*/ ~InputDesc() {}
-        const int mSessionId;
-        Vector< sp<AudioEffect> >mEffects;
-    };
-
     class AudioPolicyClient : public AudioPolicyClientInterface
     {
      public:
@@ -514,26 +462,6 @@
         const sp<IAudioPolicyServiceClient> mAudioPolicyServiceClient;
     };
 
-    static const char * const kInputSourceNames[AUDIO_SOURCE_CNT -1];
-
-    void setPreProcessorEnabled(const InputDesc *inputDesc, bool enabled);
-    status_t loadPreProcessorConfig(const char *path);
-    status_t loadEffects(cnode *root, Vector <EffectDesc *>& effects);
-    EffectDesc *loadEffect(cnode *root);
-    status_t loadInputSources(cnode *root, const Vector <EffectDesc *>& effects);
-    audio_source_t inputSourceNameToEnum(const char *name);
-    InputSourceDesc *loadInputSource(cnode *root, const Vector <EffectDesc *>& effects);
-    void loadEffectParameters(cnode *root, Vector <effect_param_t *>& params);
-    effect_param_t *loadEffectParameter(cnode *root);
-    size_t readParamValue(cnode *node,
-                          char *param,
-                          size_t *curSize,
-                          size_t *totSize);
-    size_t growParamSize(char *param,
-                         size_t size,
-                         size_t *curSize,
-                         size_t *totSize);
-
     // Internal dump utilities.
     status_t dumpPermissionDenial(int fd);
 
@@ -548,10 +476,10 @@
     AudioPolicyInterface *mAudioPolicyManager;
     AudioPolicyClient *mAudioPolicyClient;
 
-    KeyedVector< audio_source_t, InputSourceDesc* > mInputSources;
-    KeyedVector< audio_io_handle_t, InputDesc* > mInputs;
-
     DefaultKeyedVector< uid_t, sp<NotificationClient> >    mNotificationClients;
+
+    // Manage all effects configured in audio_effects.conf
+    sp<AudioPolicyEffects> mAudioPolicyEffects;
 };
 
 }; // namespace android