Merge "Add new AudioMixer processing hooks"
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..0c895ea
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/tests/Android.mk
@@ -0,0 +1,34 @@
+#
+# 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)
+
+test_name := JsonWebKeyUnittest
+test_src_dir := .
+include $(LOCAL_PATH)/unit-test.mk
+
+test_name := AesCtrDecryptorUnittest
+test_src_dir := .
+include $(LOCAL_PATH)/unit-test.mk
+
+test_name := InitDataParserUnittest
+test_src_dir := .
+include $(LOCAL_PATH)/unit-test.mk
+
+test_name :=
+test_src_dir :=
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/drm/mediadrm/plugins/clearkey/tests/unit-test.mk b/drm/mediadrm/plugins/clearkey/tests/unit-test.mk
new file mode 100644
index 0000000..e33ea52
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/tests/unit-test.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.
+#
+# -------------------------------------------------------------------
+# Makes a unit or end to end test.
+# test_name must be passed in as the base filename(without the .cpp).
+#
+$(call assert-not-null,test_name)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := $(test_name)
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    $(test_src_dir)/$(test_name).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/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;