Define error codes for MediaCodec.CryptoException

Define specific failure cases so apps have the information they
need to deal with these conditions.

Also adds a new ResourceBusyException to MediaDrm

Change-Id: Iaecf269d58108f28179974b05671bf29b9fe4b7d
related-to-bug: 10157154
related-to-bug: 9695816
diff --git a/api/current.txt b/api/current.txt
index 6bb40e1..7756bc1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12029,6 +12029,9 @@
   public static final class MediaCodec.CryptoException extends java.lang.RuntimeException {
     ctor public MediaCodec.CryptoException(int, java.lang.String);
     method public int getErrorCode();
+    field public static final int ERROR_KEY_EXPIRED = 2; // 0x2
+    field public static final int ERROR_NO_KEY = 1; // 0x1
+    field public static final int ERROR_RESOURCE_BUSY = 3; // 0x3
   }
 
   public static final class MediaCodec.CryptoInfo {
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8cf0b4b..703eb27 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -300,6 +300,24 @@
             mErrorCode = errorCode;
         }
 
+        /**
+         * This indicates that no key has been set to perform the requested
+         * decrypt operation.
+         */
+        public static final int ERROR_NO_KEY = 1;
+
+        /**
+         * This indicates that the key used for decryption is no longer
+         * valid due to license term expiration.
+         */
+        public static final int ERROR_KEY_EXPIRED = 2;
+
+        /**
+         * This indicates that a required crypto resource was not able to be
+         * allocated while attempting the requested operation.
+         */
+        public static final int ERROR_RESOURCE_BUSY = 3;
+
         public int getErrorCode() {
             return mErrorCode;
         }
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 7677d8a1..cd97ad9 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -273,6 +273,7 @@
      * Open a new session with the MediaDrm object.  A session ID is returned.
      *
      * @throws NotProvisionedException if provisioning is needed
+     * @throws ResourceBusyException if required resources are in use
      */
     public native byte[] openSession() throws NotProvisionedException;
 
@@ -379,6 +380,7 @@
      * reprovisioning is required
      * @throws DeniedByServerException if the response indicates that the
      * server rejected the request
+     * @throws ResourceBusyException if required resources are in use
      */
     public native byte[] provideKeyResponse(byte[] scope, byte[] response)
         throws NotProvisionedException, DeniedByServerException;
diff --git a/media/java/android/media/ResourceBusyException.java b/media/java/android/media/ResourceBusyException.java
new file mode 100644
index 0000000..a5abe21
--- /dev/null
+++ b/media/java/android/media/ResourceBusyException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+package android.media;
+
+/**
+ * Exception thrown when an operation on a MediaDrm object is attempted
+ * and hardware resources are not available, due to being in use.
+ */
+public final class ResourceBusyException extends MediaDrmException {
+    public ResourceBusyException(String detailMessage) {
+        super(detailMessage);
+    }
+}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index cd1d9ce..8689e19 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -49,9 +49,14 @@
     DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED     = -3,
 };
 
+struct CryptoErrorCodes {
+    jint cryptoErrorNoKey;
+    jint cryptoErrorKeyExpired;
+    jint cryptoErrorResourceBusy;
+} gCryptoErrorCodes;
+
 struct fields_t {
     jfieldID context;
-
     jfieldID cryptoInfoNumSubSamplesID;
     jfieldID cryptoInfoNumBytesOfClearDataID;
     jfieldID cryptoInfoNumBytesOfEncryptedDataID;
@@ -342,6 +347,21 @@
 
     jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error");
 
+    /* translate OS errors to Java API CryptoException errorCodes */
+    switch (err) {
+        case ERROR_DRM_NO_LICENSE:
+            err = gCryptoErrorCodes.cryptoErrorNoKey;
+            break;
+        case ERROR_DRM_LICENSE_EXPIRED:
+            err = gCryptoErrorCodes.cryptoErrorKeyExpired;
+            break;
+        case ERROR_DRM_RESOURCE_BUSY:
+            err = gCryptoErrorCodes.cryptoErrorResourceBusy;
+            break;
+        default:
+            break;
+    }
+
     jthrowable exception =
         (jthrowable)env->NewObject(clazz, constructID, err, msgObj);
 
@@ -350,9 +370,8 @@
 
 static jint throwExceptionAsNecessary(
         JNIEnv *env, status_t err, const char *msg = NULL) {
-    if (err >= ERROR_DRM_WV_VENDOR_MIN && err <= ERROR_DRM_WV_VENDOR_MAX) {
+    if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
         // We'll throw our custom MediaCodec.CryptoException
-
         throwCryptoException(env, err, msg);
         return 0;
     }
@@ -370,6 +389,12 @@
         case INFO_OUTPUT_BUFFERS_CHANGED:
             return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED;
 
+        case ERROR_DRM_NO_LICENSE:
+        case ERROR_DRM_LICENSE_EXPIRED:
+        case ERROR_DRM_RESOURCE_BUSY:
+            throwCryptoException(env, err, msg);
+            break;
+
         default:
         {
             jniThrowException(env, "java/lang/IllegalStateException", msg);
@@ -852,6 +877,22 @@
 
     gFields.cryptoInfoModeID = env->GetFieldID(clazz, "mode", "I");
     CHECK(gFields.cryptoInfoModeID != NULL);
+
+    clazz = env->FindClass("android/media/MediaCodec$CryptoException");
+    CHECK(clazz != NULL);
+
+    jfieldID field;
+    field = env->GetStaticFieldID(clazz, "ERROR_NO_KEY", "I");
+    CHECK(field != NULL);
+    gCryptoErrorCodes.cryptoErrorNoKey = env->GetStaticIntField(clazz, field);
+
+    field = env->GetStaticFieldID(clazz, "ERROR_KEY_EXPIRED", "I");
+    CHECK(field != NULL);
+    gCryptoErrorCodes.cryptoErrorKeyExpired = env->GetStaticIntField(clazz, field);
+
+    field = env->GetStaticFieldID(clazz, "ERROR_RESOURCE_BUSY", "I");
+    CHECK(field != NULL);
+    gCryptoErrorCodes.cryptoErrorResourceBusy = env->GetStaticIntField(clazz, field);
 }
 
 static void android_media_MediaCodec_native_setup(
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 7799ca4..16a1e48 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -242,6 +242,9 @@
     } else if (err == ERROR_DRM_NOT_PROVISIONED) {
         jniThrowException(env, "android/media/NotProvisionedException", msg);
         return true;
+    } else if (err == ERROR_DRM_RESOURCE_BUSY) {
+        jniThrowException(env, "android/media/ResourceBusyException", msg);
+        return true;
     } else if (err == ERROR_DRM_DEVICE_REVOKED) {
         jniThrowException(env, "android/media/DeniedByServerException", msg);
         return true;