/*
 * Copyright (C) 2007 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.
 */

/**
 * @file drm1_jni.c
 *
 * This file implement the Java Native Interface
 * for supporting OMA DRM 1.0
 */

#include <jni/drm1_jni.h>
#include <objmng/svc_drm.h>
#include "log.h"


#define MS_PER_SECOND 1000                  /* Milliseconds per second */
#define MS_PER_MINUTE 60 * MS_PER_SECOND    /* Milliseconds per minute */
#define MS_PER_HOUR   60 * MS_PER_MINUTE    /* Milliseconds per hour */
#define MS_PER_DAY    24 * MS_PER_HOUR      /* Milliseconds per day */

#define SECONDS_PER_MINUTE 60                       /* Seconds per minute*/
#define SECONDS_PER_HOUR   60 * SECONDS_PER_MINUTE  /* Seconds per hour */
#define SECONDS_PER_DAY    24 * SECONDS_PER_HOUR    /* Seconds per day */

#define DAY_PER_MONTH 30                    /* Days per month */
#define DAY_PER_YEAR  365                   /* Days per year */

/** Nonzero if 'y' is a leap year, else zero. */
#define leap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)

/** Number of leap years from 1970 to 'y' (not including 'y' itself). */
#define nleap(y) (((y) - 1969) / 4 - ((y) - 1901) / 100 + ((y) - 1601) / 400)

/** Accumulated number of days from 01-Jan up to start of current month. */
static const int32_t ydays[] = {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};

#define int64_const(s)          (s)
#define int64_add(dst, s1, s2)  ((void)((dst) = (s1) + (s2)))
#define int64_mul(dst, s1, s2)  ((void)((dst) = (int64_t)(s1) * (int64_t)(s2)))

/**
 * DRM data structure
 */
typedef struct _DrmData {
    /**
     * The id of the DRM content.
     */
    int32_t id;

    /**
     * The pointer of JNI interface.
     */
    JNIEnv* env;

    /**
     * The pointer of DRM raw content InputStream object.
     */
    jobject* pInData;

    /**
     * The len of the InputStream object.
     */
    int32_t len;

    /**
     * The next DRM data.
     */
    struct _DrmData *next;
} DrmData;

/** The table to hold all the DRM data. */
static DrmData *drmTable = NULL;

/**
 * Allocate a new item of DrmData.
 *
 * \return a pointer to a DrmData item if allocate successfully,
 *         otherwise return NULL
 */
static DrmData * newItem(void)
{
    DrmData *d = (DrmData *)malloc(sizeof(DrmData));

    if (d != NULL) {
        d->id = -1;
        d->next = NULL;
    }

    return d;
}

/**
 * Free the memory of the specified DrmData item <code>d</code>.
 *
 * \param d - a pointer to DrmData
 */
static void freeItem(DrmData *d)
{
    assert(d != NULL);

    free(d);
}

/**
 * Insert a DrmData item with given <code>name</code> into the head of
 * the DrmData list.
 *
 * @param d - the pointer of the JNI interface
 * @param pInData - the pointer of the DRM content InputStream object.
 *
 * @return <code>JNI_DRM_SUCCESS</code> if insert successfully, otherwise
 *         return <code>JNI_DRM_FAILURE</code>
 */
static int32_t addItem(DrmData* d)
{
    if (NULL == d)
        return JNI_DRM_FAILURE;

    if (NULL == drmTable) {
        drmTable = d;
        return JNI_DRM_SUCCESS;
    }

    d->next = drmTable;
    drmTable = d;

    return JNI_DRM_SUCCESS;
}

/**
 * Get the item from the DrmData list by the specified <code>
 * id</code>.
 *
 * @param p - the pointer of the DRM content InputStream object.
 *
 * @return a pointer to the DrmData item if find it successfuly,
 *         otherwise return NULL
 */
static DrmData * getItem(int32_t id)
{
    DrmData *d;

    if (NULL == drmTable)
        return NULL;

    for (d = drmTable; d != NULL; d = d->next) {
        if (id == d->id)
            return d;
    }

    return NULL;
}

/**
 * Remove the specified DrmData item <code>d</code>.
 *
 * @param p - the pointer of the DRM content InputStream object.
 *
 * @return <code>JNI_DRM_SUCCESS</code> if remove successfuly,
 *         otherwise return <code>JNI_DRM_FAILURE</code>
 */
static int32_t removeItem(int32_t id)
{
    DrmData *curItem, *preItem, *dstItem;

    if (NULL == drmTable)
        return JNI_DRM_FAILURE;

    preItem = NULL;
    for (curItem = drmTable; curItem != NULL; curItem = curItem->next) {
        if (id == curItem->id) {
            if (curItem == drmTable)
                drmTable = curItem->next;
            else
                preItem->next = curItem->next;

            freeItem(curItem);

            return JNI_DRM_SUCCESS;
        }

        preItem = curItem;
    }

    return JNI_DRM_FAILURE;
}


static int32_t getInputStreamDataLength(int32_t handle)
{
    JNIEnv* env;
    jobject* pInputStream;
    int32_t len;
    DrmData* p;
    jclass cls;
    jmethodID mid;

    p = (DrmData *)handle;

    if (NULL == p)
        return 0;

    env = p->env;
    pInputStream = p->pInData;
    len = p->len;

    if (NULL == env || p->len <= 0 || NULL == pInputStream)
        return 0;

    /* check the original InputStream is available or not */
    cls = (*env)->GetObjectClass(env, *pInputStream);
    mid = (*env)->GetMethodID(env, cls, "available", "()I");
    (*env)->DeleteLocalRef(env, cls);

    if (NULL == mid)
        return 0;

    if (0 > (*env)->CallIntMethod(env, *pInputStream, mid))
        return 0;

    return len;
}

static int32_t readInputStreamData(int32_t handle, uint8_t* buf, int32_t bufLen)
{
    JNIEnv* env;
    jobject* pInputStream;
    int32_t len;
    DrmData* p;
    jclass cls;
    jmethodID mid;
    jbyteArray tmp;
    int tmpLen;
    jbyte* pNativeBuf;

    p = (DrmData *)handle;

    if (NULL == p || NULL == buf || bufLen <- 0)
        return 0;

    env = p->env;
    pInputStream = p->pInData;
    len = p->len;

    if (NULL == env || p->len <= 0 || NULL == pInputStream)
        return 0;

    cls = (*env)->GetObjectClass(env, *pInputStream);
    mid = (*env)->GetMethodID(env, cls, "read", "([BII)I");
    tmp = (*env)->NewByteArray(env, bufLen);
    bufLen = (*env)->CallIntMethod(env, *pInputStream, mid, tmp, 0, bufLen);

    (*env)->DeleteLocalRef(env, cls);

    if (-1 == bufLen)
        return -1;

    pNativeBuf = (*env)->GetByteArrayElements(env, tmp, NULL);
    memcpy(buf, pNativeBuf, bufLen);
    (*env)->ReleaseByteArrayElements(env, tmp, pNativeBuf, 0);
    (*env)->DeleteLocalRef(env, tmp);

    return bufLen;
}

static const T_DRM_Rights_Info_Node *searchRightsObject(const jbyte* roId, const T_DRM_Rights_Info_Node* pRightsList)
{
    const T_DRM_Rights_Info_Node *pTmp;

    if (NULL == roId || NULL == pRightsList)
        return NULL;

    pTmp = pRightsList;

    while (NULL != pTmp) {
        if(0 == strcmp((char *)roId, (char *)pTmp->roInfo.roId))
            break;
        pTmp = pTmp->next;
    }

    return pTmp;
}

/**
 * Returns the difference in seconds between the given GMT time
 * and 1970-01-01 00:00:00 GMT.
 *
 * \param year the year (since 1970)
 * \param month the month (1 - 12)
 * \param day the day (1 - 31)
 * \param hour the hour (0 - 23)
 * \param minute the minute (0 - 59)
 * \param second the second (0 - 59)
 *
 * \return the difference in seconds between the given GMT time
 *         and 1970-01-01 00:00:00 GMT.
 */
static int64_t mkgmtime(
        uint32_t year, uint32_t month, uint32_t day,
        uint32_t hour, uint32_t minute, uint32_t second)
{
    int64_t result;

    /*
     * FIXME: It does not check whether the specified days
     *        is valid based on the specified months.
     */
    assert(year >= 1970
            && month > 0 && month <= 12
            && day > 0 && day <= 31
            && hour < 24 && minute < 60
            && second < 60);

    /* Set 'day' to the number of days into the year. */
    day += ydays[month - 1] + (month > 2 && leap (year)) - 1;

    /* Now calculate 'day' to the number of days since Jan 1, 1970. */
    day = day + 365 * (year - 1970) + nleap(year);

    int64_mul(result, int64_const(day), int64_const(SECONDS_PER_DAY));
    int64_add(result, result, int64_const(
        SECONDS_PER_HOUR * hour + SECONDS_PER_MINUTE * minute + second));

    return result;
}

/**
 * Compute the milliseconds by the specified <code>date</code>
 * and <code>time</code>.
 *
 * @param date - the specified date,
 *               <code>date = year * 10000 + month * 100 + day</code>
 * @param time - the specified time,
 *               <code>time = hour * 10000 + minute * 100 + second</code>
 *
 * @return the related milliseconds
 */
static int64_t computeTime(int32_t date, int32_t time)
{
    int32_t year, month, day, hour, minute, second;

    year = date / 10000;
    month = (date / 100) % 100;
    day = date % 100;
    hour = time / 10000;
    minute = (time / 100) % 100;
    second = time % 100;

    /* Adjust the invalid parameters. */
    if (year < 1970) year = 1970;
    if (month < 1) month = 1;
    if (month > 12) month = 12;
    if (day < 1) day = 1;
    if (day > 31) day = 31;
    if (hour < 0) hour = 0;
    if (hour > 23) hour = 23;
    if (minute < 0) minute = 0;
    if (minute > 59) minute = 59;
    if (second < 0) second = 0;
    if (second > 59) second = 59;

    return mkgmtime(year, month, day, hour, minute, second) * 1000;
}

/**
 * Compute the milliseconds by the specified <code>date</code>
 * and <code>time</code>.
 * Note that here we always treat 1 year as 365 days and 1 month as 30 days
 * that is not precise. But it should not be a problem since OMA DRM 2.0
 * already restricts the interval representation to be day-based,
 * i.e. there will not be an interval with year or month any more in the
 * future.
 *
 * @param date - the specified date,
 *               <code>date = year * 10000 + month * 100 + day</code>
 * @param time - the specified time,
 *               <code>time = hour * 10000 + minute * 100 + second</code>
 *
 * @return the related milliseconds
 */
static int64_t computeInterval(int32_t date, int32_t time)
{
    int32_t year, month, day, hour, minute, second;
    int64_t milliseconds;

    year = date / 10000;
    month = (date / 100) % 100;
    day = date % 100;
    hour = time / 10000;
    minute = (time / 100) % 100;
    second = time % 100;

    /* milliseconds = ((((year * 365 + month * 30 + day) * 24
     *                + hour) * 60 + minute) * 60 + second) * 1000;
     */
    int64_mul(milliseconds,
        int64_const(year * DAY_PER_YEAR + month * DAY_PER_MONTH + day),
        int64_const(MS_PER_DAY));
    int64_add(milliseconds, milliseconds,
        int64_const(hour * MS_PER_HOUR + minute * MS_PER_MINUTE +
            second * MS_PER_SECOND));

    return milliseconds;
}

static jint getObjectIntField(JNIEnv * env, jobject obj, const char *name, jint * value)
{
    jclass clazz;
    jfieldID field;

    clazz = (*env)->GetObjectClass(env, obj);
    if (NULL == clazz)
        return JNI_DRM_FAILURE;

    field = (*env)->GetFieldID(env, clazz, name, "I");
    (*env)->DeleteLocalRef(env, clazz);

    if (NULL == field)
        return JNI_DRM_FAILURE;

    *value = (*env)->GetIntField(env, obj, field);

    return JNI_DRM_SUCCESS;
}

static jint setObjectIntField(JNIEnv * env, jobject obj, const char *name, jint value)
{
    jclass clazz;
    jfieldID field;

    clazz = (*env)->GetObjectClass(env, obj);
    if (NULL == clazz)
        return JNI_DRM_FAILURE;

    field = (*env)->GetFieldID(env, clazz, name, "I");
    (*env)->DeleteLocalRef(env, clazz);

    if (NULL == field)
        return JNI_DRM_FAILURE;

    (*env)->SetIntField(env, obj, field, value);

    return JNI_DRM_SUCCESS;
}

static jint setObjectLongField(JNIEnv * env, jobject obj, const char *name, jlong value)
{
    jclass clazz;
    jfieldID field;

    clazz = (*env)->GetObjectClass(env, obj);
    if (NULL == clazz)
        return JNI_DRM_FAILURE;

    field = (*env)->GetFieldID(env, clazz, name, "J");
    (*env)->DeleteLocalRef(env, clazz);

    if (NULL == field)
        return JNI_DRM_FAILURE;

    (*env)->SetLongField(env, obj, field, value);

    return JNI_DRM_SUCCESS;
}

static jint setConstraintFields(JNIEnv * env, jobject constraint, T_DRM_Constraint_Info * pConstraint)
{
    /* if no this permission */
    if (pConstraint->indicator == (uint8_t)DRM_NO_RIGHTS) {
        if (JNI_DRM_FAILURE == setObjectIntField(env, constraint, "count", 0))
            return JNI_DRM_FAILURE;

        return JNI_DRM_SUCCESS;
    }

    /* set count field */
    if (pConstraint->indicator & DRM_COUNT_CONSTRAINT) {
        if (JNI_DRM_FAILURE == setObjectIntField(env, constraint, "count", pConstraint->count))
            return JNI_DRM_FAILURE;
    }

    /* set start time field */
    if (pConstraint->indicator & DRM_START_TIME_CONSTRAINT) {
        int64_t startTime;

        startTime = computeTime(pConstraint->startDate, pConstraint->startTime);

        if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "startDate", startTime))
            return JNI_DRM_FAILURE;
    }

    /* set end time field */
    if (pConstraint->indicator & DRM_END_TIME_CONSTRAINT) {
        int64_t endTime;

        endTime = computeTime(pConstraint->endDate, pConstraint->endTime);

        if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "endDate", endTime))
            return JNI_DRM_FAILURE;
    }

    /* set interval field */
    if (pConstraint->indicator & DRM_INTERVAL_CONSTRAINT) {
        int64_t interval;

        interval = computeInterval(pConstraint->intervalDate, pConstraint->intervalTime);

        if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "interval", interval))
            return JNI_DRM_FAILURE;
    }

    return JNI_DRM_SUCCESS;
}

static jint setRightsFields(JNIEnv * env, jobject rights, T_DRM_Rights_Info* pRoInfo)
{
    jclass clazz;
    jfieldID field;
    jstring str;
    jint index;

    clazz = (*env)->GetObjectClass(env, rights);
    if (NULL == clazz)
        return JNI_DRM_FAILURE;

    /* set roId field */
    field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;");
    (*env)->DeleteLocalRef(env, clazz);

    if (NULL == field)
        return JNI_DRM_FAILURE;

    str = (*env)->NewStringUTF(env, (char *)pRoInfo->roId);
    if (NULL == str)
        return JNI_DRM_FAILURE;

    (*env)->SetObjectField(env, rights, field, str);
    (*env)->DeleteLocalRef(env, str);

    return JNI_DRM_SUCCESS;
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent
  (JNIEnv * env, jobject rawContent, jobject data, jint len, jint mimeType)
{
    int32_t id;
    T_DRM_Input_Data inData;
    DrmData* drmInData;

    switch (mimeType) {
    case JNI_DRM_MIMETYPE_MESSAGE:
        mimeType = TYPE_DRM_MESSAGE;
        break;
    case JNI_DRM_MIMETYPE_CONTENT:
        mimeType = TYPE_DRM_CONTENT;
        break;
    default:
        return JNI_DRM_FAILURE;
    }

    drmInData = newItem();
    if (NULL == drmInData)
        return JNI_DRM_FAILURE;

    drmInData->env = env;
    drmInData->pInData = &data;
    drmInData->len = len;

    if (JNI_DRM_FAILURE == addItem(drmInData))
        return JNI_DRM_FAILURE;

    inData.inputHandle = (int32_t)drmInData;
    inData.mimeType = mimeType;
    inData.getInputDataLength = getInputStreamDataLength;
    inData.readInputData = readInputStreamData;

    id = SVC_drm_openSession(inData);
    if (id < 0)
        return JNI_DRM_FAILURE;

    drmInData->id = id;

    return id;
}

/* native interface */
JNIEXPORT jstring JNICALL
Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress
  (JNIEnv * env, jobject rawContent)
{
    jint id;
    uint8_t rightsIssuer[256] = {0};
    jstring str = NULL;

    if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id))
        return NULL;

    if (DRM_SUCCESS == SVC_drm_getRightsIssuer(id, rightsIssuer))
        str = (*env)->NewStringUTF(env, (char *)rightsIssuer);

    return str;
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod
  (JNIEnv * env, jobject rawContent)
{
    jint id;
    int32_t res;

    if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id))
        return JNI_DRM_FAILURE;

    res = SVC_drm_getDeliveryMethod(id);

    switch (res) {
    case FORWARD_LOCK:
        return JNI_DRM_FORWARD_LOCK;
    case COMBINED_DELIVERY:
        return JNI_DRM_COMBINED_DELIVERY;
    case SEPARATE_DELIVERY:
        return JNI_DRM_SEPARATE_DELIVERY;
    case SEPARATE_DELIVERY_FL:
        return JNI_DRM_SEPARATE_DELIVERY_DM;
    default:
        return JNI_DRM_FAILURE;
    }
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRawContent_nativeReadContent
  (JNIEnv * env, jobject rawContent, jbyteArray buf, jint bufOff, jint len, jint mediaOff)
{
    jint id;
    jbyte *nativeBuf;
    jclass cls;
    jmethodID mid;
    DrmData* p;
    jobject inputStream;
    jfieldID field;

    if (NULL == buf) {
        jclass newExcCls = (*env)->FindClass(env, "java/lang/NullPointerException");

        if (newExcCls == NULL)
            /* Unable to find the exception class, give up. */
            return JNI_DRM_FAILURE;

        (*env)->ThrowNew(env, newExcCls, "b is null");
    }

    if (len < 0 || bufOff < 0 || len + bufOff > (*env)->GetArrayLength(env, buf)) {
        jclass newExcCls = (*env)->FindClass(env, "java/lang/IndexOutOfBoundsException");

        if (newExcCls == NULL)
            /* Unable to find the exception class, give up. */
            return JNI_DRM_FAILURE;

        (*env)->ThrowNew(env, newExcCls, NULL);
    }

    if (mediaOff < 0 || len == 0)
        return JNI_DRM_FAILURE;

    if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id))
        return JNI_DRM_FAILURE;

    p = getItem(id);
    if (NULL == p)
        return JNI_DRM_FAILURE;

    cls = (*env)->GetObjectClass(env, rawContent);
    if (NULL == cls)
        return JNI_DRM_FAILURE;

    field = (*env)->GetFieldID(env, cls, "inData", "Ljava/io/BufferedInputStream;");
    (*env)->DeleteLocalRef(env, cls);

    if (NULL == field)
        return JNI_DRM_FAILURE;

    inputStream = (*env)->GetObjectField(env, rawContent, field);

    p->env = env;
    p->pInData = &inputStream;

    nativeBuf = (*env)->GetByteArrayElements(env, buf, NULL);

    len = SVC_drm_getContent(id, mediaOff, (uint8_t *)nativeBuf + bufOff, len);

    (*env)->ReleaseByteArrayElements(env, buf, nativeBuf, 0);

    if (DRM_MEDIA_EOF == len)
        return JNI_DRM_EOF;
    if (len <= 0)
        return JNI_DRM_FAILURE;

    return len;
}

/* native interface */
JNIEXPORT jstring JNICALL
Java_android_drm_mobile1_DrmRawContent_nativeGetContentType
  (JNIEnv * env, jobject rawContent)
{
    jint id;
    uint8_t contentType[64] = {0};
    jstring str = NULL;

    if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id))
        return NULL;

    if (DRM_SUCCESS == SVC_drm_getContentType(id, contentType))
        str = (*env)->NewStringUTF(env, (char *)contentType);

    return str;
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength
  (JNIEnv * env, jobject rawContent)
{
    jint id;
    int32_t len;

    if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id))
        return JNI_DRM_FAILURE;

    len = SVC_drm_getContentLength(id);

    if (DRM_UNKNOWN_DATA_LEN == len)
        return JNI_DRM_UNKNOWN_DATA_LEN;

    if (0 > len)
        return JNI_DRM_FAILURE;

    return len;
}

/* native interface */
JNIEXPORT void JNICALL
Java_android_drm_mobile1_DrmRawContent_finalize
  (JNIEnv * env, jobject rawContent)
{
    jint id;

    if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id))
        return;

    removeItem(id);

    SVC_drm_closeSession(id);
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo
  (JNIEnv * env, jobject rights, jint permission, jobject constraint)
{
    jclass clazz;
    jfieldID field;
    jstring str;
    uint8_t *nativeStr;
    T_DRM_Rights_Info_Node *pRightsList;
    T_DRM_Rights_Info_Node *pCurNode;
    T_DRM_Constraint_Info *pConstraint;

    clazz = (*env)->GetObjectClass(env, rights);
    if (NULL == clazz)
        return JNI_DRM_FAILURE;

    field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;");
    (*env)->DeleteLocalRef(env, clazz);

    if (NULL == field)
        return JNI_DRM_FAILURE;

    str = (*env)->GetObjectField(env, rights, field);

    nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL);
    if (NULL == nativeStr)
        return JNI_DRM_FAILURE;

    /* this means forward-lock rights */
    if (0 == strcmp((char *)nativeStr, "ForwardLock")) {
        (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);
        return JNI_DRM_SUCCESS;
    }

    if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList)) {
        (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);
        return JNI_DRM_FAILURE;
    }

    pCurNode = searchRightsObject((jbyte *)nativeStr, pRightsList);
    if (NULL == pCurNode) {
        (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);
        SVC_drm_freeRightsInfoList(pRightsList);
        return JNI_DRM_FAILURE;
    }
    (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);

    switch (permission) {
    case JNI_DRM_PERMISSION_PLAY:
        pConstraint = &(pCurNode->roInfo.playRights);
        break;
    case JNI_DRM_PERMISSION_DISPLAY:
        pConstraint = &(pCurNode->roInfo.displayRights);
        break;
    case JNI_DRM_PERMISSION_EXECUTE:
        pConstraint = &(pCurNode->roInfo.executeRights);
        break;
    case JNI_DRM_PERMISSION_PRINT:
        pConstraint = &(pCurNode->roInfo.printRights);
        break;
    default:
        SVC_drm_freeRightsInfoList(pRightsList);
        return JNI_DRM_FAILURE;
    }

    /* set constraint field */
    if (JNI_DRM_FAILURE == setConstraintFields(env, constraint, pConstraint)) {
        SVC_drm_freeRightsInfoList(pRightsList);
        return JNI_DRM_FAILURE;
    }

    SVC_drm_freeRightsInfoList(pRightsList);

    return JNI_DRM_SUCCESS;
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRights_nativeConsumeRights
  (JNIEnv * env, jobject rights, jint permission)
{
    jclass clazz;
    jfieldID field;
    jstring str;
    uint8_t *nativeStr;
    int32_t id;

    switch (permission) {
    case JNI_DRM_PERMISSION_PLAY:
        permission = DRM_PERMISSION_PLAY;
        break;
    case JNI_DRM_PERMISSION_DISPLAY:
        permission = DRM_PERMISSION_DISPLAY;
        break;
    case JNI_DRM_PERMISSION_EXECUTE:
        permission = DRM_PERMISSION_EXECUTE;
        break;
    case JNI_DRM_PERMISSION_PRINT:
        permission = DRM_PERMISSION_PRINT;
        break;
    default:
        return JNI_DRM_FAILURE;
    }

    clazz = (*env)->GetObjectClass(env, rights);
    if (NULL == clazz)
        return JNI_DRM_FAILURE;

    field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;");
    (*env)->DeleteLocalRef(env, clazz);

    if (NULL == field)
        return JNI_DRM_FAILURE;

    str = (*env)->GetObjectField(env, rights, field);

    nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL);
    if (NULL == nativeStr)
        return JNI_DRM_FAILURE;

    if (0 == strcmp("ForwardLock", (char *)nativeStr)) {
        (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);
        return JNI_DRM_SUCCESS;
    }

    if (DRM_SUCCESS != SVC_drm_updateRights(nativeStr, permission)) {
        (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);
        return JNI_DRM_FAILURE;
    }

    (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);

    return JNI_DRM_SUCCESS;
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights
  (JNIEnv * env, jobject rightsManager, jobject data, jint len, jint mimeType, jobject rights)
{
    int32_t id;
    T_DRM_Input_Data inData;
    DrmData* drmInData;
    jclass cls;
    jmethodID mid;
    T_DRM_Rights_Info rightsInfo;

    switch (mimeType) {
    case JNI_DRM_MIMETYPE_RIGHTS_XML:
        mimeType = TYPE_DRM_RIGHTS_XML;
        break;
    case JNI_DRM_MIMETYPE_RIGHTS_WBXML:
        mimeType = TYPE_DRM_RIGHTS_WBXML;
        break;
    case JNI_DRM_MIMETYPE_MESSAGE:
        mimeType = TYPE_DRM_MESSAGE;
        break;
    default:
        return JNI_DRM_FAILURE;
    }

    drmInData = newItem();
    if (NULL == drmInData)
        return JNI_DRM_FAILURE;

    drmInData->env = env;
    drmInData->pInData = &data;
    drmInData->len = len;

    inData.inputHandle = (int32_t)drmInData;
    inData.mimeType = mimeType;
    inData.getInputDataLength = getInputStreamDataLength;
    inData.readInputData = readInputStreamData;

    memset(&rightsInfo, 0, sizeof(T_DRM_Rights_Info));
    if (DRM_FAILURE == SVC_drm_installRights(inData, &rightsInfo))
        return JNI_DRM_FAILURE;

    freeItem(drmInData);

    return setRightsFields(env, rights, &rightsInfo);
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights
  (JNIEnv * env, jobject rightsManager, jobject rawContent, jobject rights)
{
    jint id;
    T_DRM_Rights_Info rightsInfo;

    if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id))
        return JNI_DRM_FAILURE;

    memset(&rightsInfo, 0, sizeof(T_DRM_Rights_Info));
    if (DRM_SUCCESS != SVC_drm_getRightsInfo(id, &rightsInfo))
        return JNI_DRM_FAILURE;

    return setRightsFields(env, rights, &rightsInfo);
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights
  (JNIEnv * env, jobject rightsManager)
{
    T_DRM_Rights_Info_Node *pRightsList;
    T_DRM_Rights_Info_Node *pCurNode;
    int32_t num = 0;

    if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList))
        return JNI_DRM_FAILURE;

    pCurNode = pRightsList;
    while (pCurNode != NULL) {
        num++;
        pCurNode = pCurNode->next;
    }

    SVC_drm_freeRightsInfoList(pRightsList);

    return num;
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList
  (JNIEnv * env, jobject rightsManager, jobjectArray rightsArray, jint num)
{
    T_DRM_Rights_Info_Node *pRightsList;
    T_DRM_Rights_Info_Node *pCurNode;
    int32_t index;

    if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList))
        return JNI_DRM_FAILURE;

    pCurNode = pRightsList;
    for (index = 0; NULL != pCurNode; index++) {
        jobject rights = (*env)->GetObjectArrayElement(env, rightsArray, index);
        if (NULL == rights)
            break;

        if (JNI_DRM_FAILURE == setRightsFields(env, rights, &(pCurNode->roInfo)))
            break;

        (*env)->SetObjectArrayElement(env, rightsArray, index, rights);

        pCurNode = pCurNode->next;
    }

    SVC_drm_freeRightsInfoList(pRightsList);

    return index;
}

/* native interface */
JNIEXPORT jint JNICALL
Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights
  (JNIEnv * env, jobject rightsManager, jobject rights)
{
    jclass clazz;
    jfieldID field;
    jstring str;
    uint8_t *nativeStr;

    clazz = (*env)->GetObjectClass(env, rights);
    if (NULL == clazz)
        return JNI_DRM_FAILURE;

    field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;");
    if (NULL == field)
        return JNI_DRM_FAILURE;

    str = (*env)->GetObjectField(env, rights, field);

    nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL);
    if (NULL == nativeStr)
        return JNI_DRM_FAILURE;

    if (0 == strcmp("ForwardLock", (char *)nativeStr))
        return JNI_DRM_SUCCESS;

    if (DRM_SUCCESS != SVC_drm_deleteRights(nativeStr)) {
        (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);
        return JNI_DRM_FAILURE;
    }

    (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr);
    return JNI_DRM_SUCCESS;
}

/*
 * Table of methods associated with the DrmRawContent class.
 */
static JNINativeMethod gDrmRawContentMethods[] = {
    /* name, signature, funcPtr */
    {"nativeConstructDrmContent", "(Ljava/io/InputStream;II)I",
        (void*)Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent},
    {"nativeGetRightsAddress", "()Ljava/lang/String;",
        (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress},
    {"nativeGetDeliveryMethod", "()I",
        (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod},
    {"nativeReadContent", "([BIII)I",
        (void*)Java_android_drm_mobile1_DrmRawContent_nativeReadContent},
    {"nativeGetContentType", "()Ljava/lang/String;",
        (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetContentType},
    {"nativeGetContentLength", "()I",
        (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength},
    {"finalize", "()V",
        (void*)Java_android_drm_mobile1_DrmRawContent_finalize},
};

/*
 * Table of methods associated with the DrmRights class.
 */
static JNINativeMethod gDrmRightsMethods[] = {
    /* name, signature, funcPtr */
    {"nativeGetConstraintInfo", "(ILandroid/drm/mobile1/DrmConstraintInfo;)I",
        (void*)Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo},
    {"nativeConsumeRights", "(I)I",
        (void*)Java_android_drm_mobile1_DrmRights_nativeConsumeRights},
};

/*
 * Table of methods associated with the DrmRightsManager class.
 */
static JNINativeMethod gDrmRightsManagerMethods[] = {
    /* name, signature, funcPtr */
    {"nativeInstallDrmRights", "(Ljava/io/InputStream;IILandroid/drm/mobile1/DrmRights;)I",
        (void*)Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights},
    {"nativeQueryRights", "(Landroid/drm/mobile1/DrmRawContent;Landroid/drm/mobile1/DrmRights;)I",
        (void*)Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights},
    {"nativeGetNumOfRights", "()I",
        (void*)Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights},
    {"nativeGetRightsList", "([Landroid/drm/mobile1/DrmRights;I)I",
        (void*)Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList},
    {"nativeDeleteRights", "(Landroid/drm/mobile1/DrmRights;)I",
        (void*)Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights},
};

/*
 * Register several native methods for one class.
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL)
        return JNI_FALSE;

    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0)
        return JNI_FALSE;

    return JNI_TRUE;
}

/*
 * Register native methods for all classes we know about.
 */
static int registerNatives(JNIEnv* env)
{
    if (!registerNativeMethods(env, "android/drm/mobile1/DrmRawContent",
            gDrmRawContentMethods, sizeof(gDrmRawContentMethods) / sizeof(gDrmRawContentMethods[0])))
        return JNI_FALSE;

    if (!registerNativeMethods(env, "android/drm/mobile1/DrmRights",
            gDrmRightsMethods, sizeof(gDrmRightsMethods) / sizeof(gDrmRightsMethods[0])))
        return JNI_FALSE;

    if (!registerNativeMethods(env, "android/drm/mobile1/DrmRightsManager",
            gDrmRightsManagerMethods, sizeof(gDrmRightsManagerMethods) / sizeof(gDrmRightsManagerMethods[0])))
        return JNI_FALSE;

    return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    printf("Entering JNI_OnLoad\n");

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK)
        goto bail;

    assert(env != NULL);

    if (!registerNatives(env))
        goto bail;

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    printf("Leaving JNI_OnLoad (result=0x%x)\n", result);
    return result;
}
