Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5a4c733e authored by Arun Johnson's avatar Arun Johnson
Browse files

CryptoAsync: Creating MediaCodec.CryptoException

CryptoException is created with MediaCodec.CryptoInfo
Added callback onCryptoError(MediaCodec.CryptoException)
as another MediaCodec callback to signal decryption errors.

Bug: 254050543

Change-Id: Ifb8bfb44b38178ec99e6dbb607825c642bd4e46f
parent 64ecf87b
Loading
Loading
Loading
Loading
+3 −0
Original line number Original line Diff line number Diff line
@@ -22143,6 +22143,7 @@ package android.media {
    field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
    field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
    field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
    field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
    field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2
    field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2
    field public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4; // 0x4
    field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
    field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
    field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
    field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
    field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
    field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
@@ -22172,6 +22173,7 @@ package android.media {
  public abstract static class MediaCodec.Callback {
  public abstract static class MediaCodec.Callback {
    ctor public MediaCodec.Callback();
    ctor public MediaCodec.Callback();
    method public void onCryptoError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CryptoException);
    method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException);
    method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException);
    method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int);
    method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int);
    method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo);
    method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo);
@@ -22189,6 +22191,7 @@ package android.media {
  public static final class MediaCodec.CryptoException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
  public static final class MediaCodec.CryptoException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
    ctor public MediaCodec.CryptoException(int, @Nullable String);
    ctor public MediaCodec.CryptoException(int, @Nullable String);
    method @Nullable public android.media.MediaCodec.CryptoInfo getCryptoInfo();
    method public int getErrorCode();
    method public int getErrorCode();
    field @Deprecated public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8
    field @Deprecated public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8
    field @Deprecated public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4
    field @Deprecated public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4
+57 −2
Original line number Original line Diff line number Diff line
@@ -571,6 +571,10 @@ import java.util.concurrent.locks.ReentrantLock;
   void onError(…) {
   void onError(…) {
     …
     …
   }
   }
   {@literal @Override}
   void onCryptoError(…) {
     …
   }
 });
 });
 codec.configure(format, …);
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 mOutputFormat = codec.getOutputFormat(); // option B
@@ -1774,6 +1778,7 @@ final public class MediaCodec {
    private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
    private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
    private static final String EOS_AND_DECODE_ONLY_ERROR_MESSAGE = "An input buffer cannot have "
    private static final String EOS_AND_DECODE_ONLY_ERROR_MESSAGE = "An input buffer cannot have "
            + "both BUFFER_FLAG_END_OF_STREAM and BUFFER_FLAG_DECODE_ONLY flags";
            + "both BUFFER_FLAG_END_OF_STREAM and BUFFER_FLAG_DECODE_ONLY flags";
    private static final int CB_CRYPTO_ERROR = 6;


    private class EventHandler extends Handler {
    private class EventHandler extends Handler {
        private MediaCodec mCodec;
        private MediaCodec mCodec;
@@ -1901,6 +1906,12 @@ final public class MediaCodec {
                    break;
                    break;
                }
                }


                case CB_CRYPTO_ERROR:
                {
                    mCallback.onCryptoError(mCodec, (MediaCodec.CryptoException) msg.obj);
                    break;
                }

                case CB_OUTPUT_FORMAT_CHANGE:
                case CB_OUTPUT_FORMAT_CHANGE:
                {
                {
                    mCallback.onOutputFormatChanged(mCodec,
                    mCallback.onOutputFormatChanged(mCodec,
@@ -2104,12 +2115,25 @@ final public class MediaCodec {
     */
     */
    public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2;
    public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2;


    /**
     * This flag should be used on a secure decoder only. MediaCodec configured with this
     * flag does decryption in a separate thread. The flag requires MediaCodec to operate
     * asynchronously and will throw CryptoException if any, in the onCryptoError()
     * callback. Applications should override the default implementation of
     * onCryptoError() and access the associated CryptoException.
     *
     * CryptoException thrown will contain {@link MediaCodec.CryptoInfo}
     * This can be accessed using getCryptoInfo()
     */
    public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4;

    /** @hide */
    /** @hide */
    @IntDef(
    @IntDef(
        flag = true,
        flag = true,
        value = {
        value = {
            CONFIGURE_FLAG_ENCODE,
            CONFIGURE_FLAG_ENCODE,
            CONFIGURE_FLAG_USE_BLOCK_MODEL,
            CONFIGURE_FLAG_USE_BLOCK_MODEL,
            CONFIGURE_FLAG_USE_CRYPTO_ASYNC,
    })
    })
    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface ConfigureFlag {}
    public @interface ConfigureFlag {}
@@ -2523,19 +2547,20 @@ final public class MediaCodec {
    public final static class CryptoException extends RuntimeException
    public final static class CryptoException extends RuntimeException
            implements MediaDrmThrowable {
            implements MediaDrmThrowable {
        public CryptoException(int errorCode, @Nullable String detailMessage) {
        public CryptoException(int errorCode, @Nullable String detailMessage) {
            this(detailMessage, errorCode, 0, 0, 0);
            this(detailMessage, errorCode, 0, 0, 0, null);
        }
        }


        /**
        /**
         * @hide
         * @hide
         */
         */
        public CryptoException(String message, int errorCode, int vendorError, int oemError,
        public CryptoException(String message, int errorCode, int vendorError, int oemError,
                int errorContext) {
                int errorContext, @Nullable CryptoInfo cryptoInfo) {
            super(message);
            super(message);
            mErrorCode = errorCode;
            mErrorCode = errorCode;
            mVendorError = vendorError;
            mVendorError = vendorError;
            mOemError = oemError;
            mOemError = oemError;
            mErrorContext = errorContext;
            mErrorContext = errorContext;
            mCryptoInfo = cryptoInfo;
        }
        }


        /**
        /**
@@ -2654,6 +2679,16 @@ final public class MediaCodec {
            return mErrorCode;
            return mErrorCode;
        }
        }


        /**
         * Returns CryptoInfo associated with this {@link CryptoException}
         * if any
         *
         * @return CryptoInfo object if any. {@link MediaCodec.CryptoException}
         */
        public @Nullable CryptoInfo getCryptoInfo() {
            return mCryptoInfo;
        }

        @Override
        @Override
        public int getVendorError() {
        public int getVendorError() {
            return mVendorError;
            return mVendorError;
@@ -2670,6 +2705,7 @@ final public class MediaCodec {
        }
        }


        private final int mErrorCode, mVendorError, mOemError, mErrorContext;
        private final int mErrorCode, mVendorError, mOemError, mErrorContext;
        private CryptoInfo mCryptoInfo;
    }
    }


    /**
    /**
@@ -5087,6 +5123,25 @@ final public class MediaCodec {
         */
         */
        public abstract void onError(@NonNull MediaCodec codec, @NonNull CodecException e);
        public abstract void onError(@NonNull MediaCodec codec, @NonNull CodecException e);


        /**
         * Called only when MediaCodec encountered a crypto(decryption) error when using
         * a decoder configured with CONFIGURE_FLAG_USE_CRYPTO_ASYNC flag along with crypto
         * or descrambler object.
         *
         * @param codec The MediaCodec object
         * @param e The {@link MediaCodec.CryptoException} object with error details.
         */
        public void onCryptoError(@NonNull MediaCodec codec, @NonNull CryptoException e) {
            /*
             * A default implementation for backward compatibility.
             * Use of CONFIGURE_FLAG_USE_CRYPTO_ASYNC requires override of this callback
             * to receive CrytoInfo. Without an orverride an exception is thrown.
             */
            throw new IllegalStateException(
                    "Client must override onCryptoError when the codec is " +
                    "configured with CONFIGURE_FLAG_USE_CRYPTO_ASYNC.", e);
        }

        /**
        /**
         * Called when the output format has changed
         * Called when the output format has changed
         *
         *
+196 −66
Original line number Original line Diff line number Diff line
@@ -184,6 +184,8 @@ struct fields_t {
    jmethodID postEventFromNativeID;
    jmethodID postEventFromNativeID;
    jmethodID lockAndGetContextID;
    jmethodID lockAndGetContextID;
    jmethodID setAndUnlockContextID;
    jmethodID setAndUnlockContextID;
    jmethodID cryptoInfoSetID;
    jmethodID cryptoInfoSetPatternID;
    jfieldID cryptoInfoNumSubSamplesID;
    jfieldID cryptoInfoNumSubSamplesID;
    jfieldID cryptoInfoNumBytesOfClearDataID;
    jfieldID cryptoInfoNumBytesOfClearDataID;
    jfieldID cryptoInfoNumBytesOfEncryptedDataID;
    jfieldID cryptoInfoNumBytesOfEncryptedDataID;
@@ -203,6 +205,7 @@ struct fields_t {
static fields_t gFields;
static fields_t gFields;
static const void *sRefBaseOwner;
static const void *sRefBaseOwner;


jint MediaErrorToJavaError(status_t err);


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////


@@ -1068,6 +1071,180 @@ static jthrowable createCodecException(
    return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get());
    return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get());
}
}


static void AMessageToCryptoInfo(JNIEnv * env, const jobject & obj,
        const sp<AMessage> & msg) {
    if(msg == nullptr || obj == nullptr) {
        ALOGE("CryptoAsync Nothing to do in AMessagetoCryptoInfo");
        return;
    }
    size_t numSubSamples = 0;
    sp<ABuffer> subSamplesBuffer;
    sp<ABuffer> keyBuffer;
    sp<ABuffer> ivBuffer;
    CryptoPlugin::Mode mode;
    CryptoPlugin::Pattern pattern;
    CHECK(msg->findInt32("mode", (int*)&mode));
    CHECK(msg->findSize("numSubSamples", &numSubSamples));
    CHECK(msg->findBuffer("subSamples", &subSamplesBuffer));
    CHECK(msg->findInt32("encryptBlocks", (int32_t *)&pattern.mEncryptBlocks));
    CHECK(msg->findInt32("skipBlocks", (int32_t *)&pattern.mSkipBlocks));
    CHECK(msg->findBuffer("iv", &ivBuffer));
    CHECK(msg->findBuffer("key", &keyBuffer));

    // subsamples
    ScopedLocalRef<jintArray> samplesOfEncryptedDataArr(env, env->NewIntArray(numSubSamples));
    ScopedLocalRef<jintArray> samplesOfClearDataArr(env, env->NewIntArray(numSubSamples));
    jboolean isCopy;
    jint *dstEncryptedSamples =
        env->GetIntArrayElements(samplesOfEncryptedDataArr.get(), &isCopy);
    jint * dstClearSamples =
        env->GetIntArrayElements(samplesOfClearDataArr.get(), &isCopy);

    CryptoPlugin::SubSample * samplesArray =
        (CryptoPlugin::SubSample*)(subSamplesBuffer.get()->data());

    for(int i = 0 ; i < numSubSamples ; i++) {
        dstEncryptedSamples[i] = samplesArray[i].mNumBytesOfEncryptedData;
        dstClearSamples[i] = samplesArray[i].mNumBytesOfClearData;
    }
    env->ReleaseIntArrayElements(samplesOfEncryptedDataArr.get(), dstEncryptedSamples, 0);
    env->ReleaseIntArrayElements(samplesOfClearDataArr.get(), dstClearSamples, 0);
    // key and iv
    jbyteArray keyArray = NULL;
    jbyteArray ivArray = NULL;
    if (keyBuffer.get() != nullptr && keyBuffer->size() > 0) {
        keyArray = env->NewByteArray(keyBuffer->size());
        jbyte * dstKey = env->GetByteArrayElements(keyArray, &isCopy);
        memcpy(dstKey, keyBuffer->data(), keyBuffer->size());
        env->ReleaseByteArrayElements(keyArray,dstKey,0);
    }
    if (ivBuffer.get() != nullptr && ivBuffer->size() > 0) {
        ivArray = env->NewByteArray(ivBuffer->size());
        jbyte *dstIv = env->GetByteArrayElements(ivArray, &isCopy);
        memcpy(dstIv, ivBuffer->data(), ivBuffer->size());
        env->ReleaseByteArrayElements(ivArray, dstIv,0);
    }
    // set samples, key and iv
    env->CallVoidMethod(
        obj,
        gFields.cryptoInfoSetID,
        (jint)numSubSamples,
        samplesOfClearDataArr.get(),
        samplesOfEncryptedDataArr.get(),
        keyArray,
        ivArray,
        mode);
    if (keyArray != NULL) {
        env->DeleteLocalRef(keyArray);
    }
    if (ivArray != NULL) {
        env->DeleteLocalRef(ivArray);
    }
    // set pattern
    env->CallVoidMethod(
        obj,
        gFields.cryptoInfoSetPatternID,
        pattern.mEncryptBlocks,
        pattern.mSkipBlocks);
}

static void CryptoErrorToJavaError(status_t err, jint& jerr, std::string& defaultMsg) {
    switch(err) {
        case ERROR_DRM_NO_LICENSE:
            jerr = gCryptoErrorCodes.cryptoErrorNoKey;
            defaultMsg = "Crypto key not available";
            break;
        case ERROR_DRM_LICENSE_EXPIRED:
            jerr = gCryptoErrorCodes.cryptoErrorKeyExpired;
            defaultMsg = "License expired";
            break;
        case ERROR_DRM_RESOURCE_BUSY:
            jerr = gCryptoErrorCodes.cryptoErrorResourceBusy;
            defaultMsg = "Resource busy or unavailable";
            break;
        case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION:
            jerr = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection;
            defaultMsg = "Required output protections are not active";
            break;
        case ERROR_DRM_SESSION_NOT_OPENED:
            jerr = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
            defaultMsg = "Attempted to use a closed session";
            break;
        case ERROR_DRM_INSUFFICIENT_SECURITY:
            jerr = gCryptoErrorCodes.cryptoErrorInsufficientSecurity;
            defaultMsg = "Required security level is not met";
            break;
        case ERROR_DRM_CANNOT_HANDLE:
            jerr = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
            defaultMsg = "Operation not supported in this configuration";
            break;
        case ERROR_DRM_FRAME_TOO_LARGE:
            jerr = gCryptoErrorCodes.cryptoErrorFrameTooLarge;
            defaultMsg = "Decrytped frame exceeds size of output buffer";
            break;
        case ERROR_DRM_SESSION_LOST_STATE:
            jerr = gCryptoErrorCodes.cryptoErrorLostState;
            defaultMsg = "Session state was lost, open a new session and retry";
            break;
        default:  // Other negative DRM error codes go out best-effort.
            jerr = MediaErrorToJavaError(err);
            defaultMsg = StrCryptoError(err);
            break;
    }
}
static jthrowable createCryptoException(JNIEnv *env, status_t err,
        const char * msg = NULL, const sp<ICrypto> & crypto = NULL,
    const sp<AMessage> & cryptoInfo = NULL) {
    jthrowable exception = nullptr;
    jmethodID constructID = nullptr;
    ScopedLocalRef<jobject> cryptoInfoObject(env);
    std::string defaultMsg = "Unknown Error";
    jint jerr = 0;
    // Get a class ref for CryptoException
    ScopedLocalRef<jclass> clazz(
        env, env->FindClass("android/media/MediaCodec$CryptoException"));
    CHECK(clazz.get() != NULL);

    // Get constructor ref for CryptoException
    constructID = env->GetMethodID(clazz.get(), "<init>",
            "(Ljava/lang/String;IIIILandroid/media/MediaCodec$CryptoInfo;)V");
    CHECK(constructID != NULL);

    // create detailed message for exception
    CryptoErrorToJavaError(err, jerr, defaultMsg);
    std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
    DrmStatus dStatus(err, originalMsg.c_str());
    std::string detailedMsg(
            DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
    jstring msgObj = env->NewStringUTF(detailedMsg.c_str());

    if (cryptoInfo != nullptr) {
        // Class ref for CryptoInfo
        ScopedLocalRef<jclass> clazzCryptoInfo(
                env, env->FindClass("android/media/MediaCodec$CryptoInfo"));
        CHECK(clazzCryptoInfo.get() != NULL);

        // Constructor reference for CryptoInfo
        jmethodID constructCryptoInfo =
                env->GetMethodID(clazzCryptoInfo.get(), "<init>", "()V");
        CHECK(constructCryptoInfo != NULL);

        // Create CryptoInfo jobject
        cryptoInfoObject.reset(
                env->NewObject(clazzCryptoInfo.get(), constructCryptoInfo));
        CHECK(cryptoInfoObject.get() != NULL);

        // Translate AMesage to CryptoInfo
        AMessageToCryptoInfo(env, cryptoInfoObject.get(), cryptoInfo);
    }

    exception = (jthrowable)env->NewObject(
            clazz.get(), constructID, msgObj, jerr,
            dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext(),
            cryptoInfoObject.get());

    return exception;
}
void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
    int32_t arg1, arg2 = 0;
    int32_t arg1, arg2 = 0;
    jobject obj = NULL;
    jobject obj = NULL;
@@ -1107,6 +1284,17 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
            break;
            break;
        }
        }


        case MediaCodec::CB_CRYPTO_ERROR:
        {
            int32_t err, actionCode;
            AString errorDetail;
            CHECK(msg->findInt32("err", &err));
            CHECK(msg->findInt32("actionCode",&actionCode));
            CHECK(msg->findString("errorDetail", &errorDetail));
            obj = (jobject)createCryptoException(env, err, errorDetail.c_str(), NULL, msg);
            break;
        }

        case MediaCodec::CB_ERROR:
        case MediaCodec::CB_ERROR:
        {
        {
            int32_t err, actionCode;
            int32_t err, actionCode;
@@ -1144,7 +1332,6 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
        default:
        default:
            TRESPASS();
            TRESPASS();
    }
    }

    env->CallVoidMethod(
    env->CallVoidMethod(
            mObject,
            mObject,
            gFields.postEventFromNativeID,
            gFields.postEventFromNativeID,
@@ -1229,7 +1416,6 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    }
    }
}
}


jint MediaErrorToJavaError(status_t err);


}  // namespace android
}  // namespace android


@@ -1280,70 +1466,8 @@ static void throwCodecException(JNIEnv *env, status_t err, int32_t actionCode, c


static void throwCryptoException(JNIEnv *env, status_t err, const char *msg,
static void throwCryptoException(JNIEnv *env, status_t err, const char *msg,
        const sp<ICrypto> &crypto) {
        const sp<ICrypto> &crypto) {
    ScopedLocalRef<jclass> clazz(
    jthrowable exception = createCryptoException(
            env, env->FindClass("android/media/MediaCodec$CryptoException"));
            env, err, msg, crypto, /* cryptoInfo */ NULL);
    CHECK(clazz.get() != NULL);

    jmethodID constructID =
        env->GetMethodID(clazz.get(), "<init>", "(Ljava/lang/String;IIII)V");
    CHECK(constructID != NULL);

    std::string defaultMsg = "Unknown Error";

    /* translate OS errors to Java API CryptoException errorCodes (which are positive) */
    jint jerr = 0;
    switch (err) {
        case ERROR_DRM_NO_LICENSE:
            jerr = gCryptoErrorCodes.cryptoErrorNoKey;
            defaultMsg = "Crypto key not available";
            break;
        case ERROR_DRM_LICENSE_EXPIRED:
            jerr = gCryptoErrorCodes.cryptoErrorKeyExpired;
            defaultMsg = "License expired";
            break;
        case ERROR_DRM_RESOURCE_BUSY:
            jerr = gCryptoErrorCodes.cryptoErrorResourceBusy;
            defaultMsg = "Resource busy or unavailable";
            break;
        case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION:
            jerr = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection;
            defaultMsg = "Required output protections are not active";
            break;
        case ERROR_DRM_SESSION_NOT_OPENED:
            jerr = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
            defaultMsg = "Attempted to use a closed session";
            break;
        case ERROR_DRM_INSUFFICIENT_SECURITY:
            jerr = gCryptoErrorCodes.cryptoErrorInsufficientSecurity;
            defaultMsg = "Required security level is not met";
            break;
        case ERROR_DRM_CANNOT_HANDLE:
            jerr = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
            defaultMsg = "Operation not supported in this configuration";
            break;
        case ERROR_DRM_FRAME_TOO_LARGE:
            jerr = gCryptoErrorCodes.cryptoErrorFrameTooLarge;
            defaultMsg = "Decrytped frame exceeds size of output buffer";
            break;
        case ERROR_DRM_SESSION_LOST_STATE:
            jerr = gCryptoErrorCodes.cryptoErrorLostState;
            defaultMsg = "Session state was lost, open a new session and retry";
            break;
        default:  /* Other negative DRM error codes go out best-effort. */
            jerr = MediaErrorToJavaError(err);
            defaultMsg = StrCryptoError(err);
            break;
    }

    std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
    DrmStatus dStatus(err, originalMsg.c_str());
    std::string detailedMsg(DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
    jstring msgObj = env->NewStringUTF(detailedMsg.c_str());

    jthrowable exception =
        (jthrowable)env->NewObject(clazz.get(), constructID, msgObj, jerr,
                                   dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext());

    env->Throw(exception);
    env->Throw(exception);
}
}


@@ -2963,6 +3087,12 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
    clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo"));
    clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo"));
    CHECK(clazz.get() != NULL);
    CHECK(clazz.get() != NULL);


    gFields.cryptoInfoSetID = env->GetMethodID(clazz.get(), "set", "(I[I[I[B[BI)V");
    CHECK(gFields.cryptoInfoSetID != NULL);

    gFields.cryptoInfoSetPatternID = env->GetMethodID(clazz.get(), "setPattern", "(II)V");
    CHECK(gFields.cryptoInfoSetPatternID != NULL);

    gFields.cryptoInfoNumSubSamplesID =
    gFields.cryptoInfoNumSubSamplesID =
        env->GetFieldID(clazz.get(), "numSubSamples", "I");
        env->GetFieldID(clazz.get(), "numSubSamples", "I");
    CHECK(gFields.cryptoInfoNumSubSamplesID != NULL);
    CHECK(gFields.cryptoInfoNumSubSamplesID != NULL);