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

Commit 55d26249 authored by Jeff Tinker's avatar Jeff Tinker
Browse files

Add new offline management APIs to MediaDrm

bug:110838441
bug:117570956
bug:116252891

test: android.media.cts.MediaDrmClearkeyTest#testOfflineKeyManagement
      make offline-sdk-docs

Change-Id: I5561502c308fbdc2b669120c3c7e8c0544b13b59
parent b8d66722
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -24190,6 +24190,8 @@ package android.media {
    method public static int getMaxSecurityLevel();
    method public int getMaxSessionCount();
    method public android.os.PersistableBundle getMetrics();
    method public java.util.List<byte[]> getOfflineLicenseKeySetIds();
    method public int getOfflineLicenseState(byte[]);
    method public int getOpenSessionCount();
    method public byte[] getPropertyByteArray(java.lang.String);
    method public java.lang.String getPropertyString(java.lang.String);
@@ -24210,6 +24212,7 @@ package android.media {
    method public void releaseSecureStops(byte[]);
    method public void removeAllSecureStops();
    method public void removeKeys(byte[]);
    method public void removeOfflineLicense(byte[]);
    method public void removeSecureStop(byte[]);
    method public void restoreKeys(byte[], byte[]);
    method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
@@ -24232,6 +24235,9 @@ package android.media {
    field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
    field public static final int KEY_TYPE_RELEASE = 3; // 0x3
    field public static final int KEY_TYPE_STREAMING = 1; // 0x1
    field public static final int OFFLINE_LICENSE_INACTIVE = 2; // 0x2
    field public static final int OFFLINE_LICENSE_STATE_UNKNOWN = 0; // 0x0
    field public static final int OFFLINE_LICENSE_USABLE = 1; // 0x1
    field public static final java.lang.String PROPERTY_ALGORITHMS = "algorithms";
    field public static final java.lang.String PROPERTY_DESCRIPTION = "description";
    field public static final java.lang.String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId";
+81 −0
Original line number Diff line number Diff line
@@ -978,6 +978,87 @@ public final class MediaDrm implements AutoCloseable {
    private native Certificate provideProvisionResponseNative(@NonNull byte[] response)
            throws DeniedByServerException;

    /**
     * The keys in an offline license allow protected content to be played even
     * if the device is not connected to a network. Offline licenses are stored
     * on the device after a key request/response exchange when the key request
     * KeyType is OFFLINE. Normally each app is responsible for keeping track of
     * the keySetIds it has created. If an app loses the keySetId for any stored
     * licenses that it created, however, it must be able to recover the stored
     * keySetIds so those licenses can be removed when they expire or when the
     * app is uninstalled.
     * <p>
     * This method returns a list of the keySetIds for all offline licenses.
     * The offline license keySetId may be used to query the status of an
     * offline license with {@link #getOfflineLicenseState} or remove it with
     * {@link #removeOfflineLicense}.
     *
     * @return a list of offline license keySetIds
     */
    @NonNull
    public native List<byte[]> getOfflineLicenseKeySetIds();

    /**
     * Normally offline licenses are released using a key request/response
     * exchange using {@link #getKeyRequest} where the key type is
     * KEY_TYPE_RELEASE, followed by {@link #provideKeyResponse}. This allows
     * the server to cryptographically confirm that the license has been removed
     * and then adjust the count of offline licenses allocated to the device.
     * <p>
     * In some exceptional situations it may be necessary to directly remove
     * offline licenses without notifying the server, which may be performed
     * using this method.
     *
     * @param keySetId the id of the offline license to remove
     * @throws IllegalArgumentException if the keySetId does not refer to an
     * offline license.
     */
    public native void removeOfflineLicense(@NonNull byte[] keySetId);

    /**
     * Offline license state is unknown, an error occurred while trying
     * to access it.
     */
    public static final int OFFLINE_LICENSE_STATE_UNKNOWN = 0;

    /**
     * Offline license state is usable, the keys may be used for decryption.
     */
    public static final int OFFLINE_LICENSE_USABLE = 1;

    /**
     * Offline license state is inactive, the keys have been marked for
     * release using {@link #getKeyRequest} with KEY_TYPE_RELEASE but the
     * key response has not been received.
     */
    public static final int OFFLINE_LICENSE_INACTIVE = 2;

    /** @hide */
    @IntDef({
        OFFLINE_LICENSE_STATE_UNKNOWN,
        OFFLINE_LICENSE_USABLE,
        OFFLINE_LICENSE_INACTIVE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface OfflineLicenseState {}

    /**
     * Request the state of an offline license. An offline license may be usable
     * or inactive. The keys in a usable offline license are available for
     * decryption. When the offline license state is inactive, the keys have
     * been marked for release using {@link #getKeyRequest} with
     * KEY_TYPE_RELEASE but the key response has not been received. The keys in
     * an inactive offline license are not usable for decryption.
     *
     * @param keySetId selects the offline license
     * @return the offline license state, one of {@link #OFFLINE_LICENSE_USABLE},
     * {@link #OFFLINE_LICENSE_INACTIVE} or {@link #OFFLINE_LICENSE_STATE_UNKNOWN}.
     * @throws IllegalArgumentException if the keySetId does not refer to an
     * offline license.
     */
    @OfflineLicenseState
    public native int getOfflineLicenseState(@NonNull byte[] keySetId);

    /**
     * Secure stops are a way to enforce limits on the number of concurrent
     * streams per subscriber across devices. They provide secure monitoring of
+92 −12
Original line number Diff line number Diff line
@@ -161,6 +161,12 @@ struct SecurityLevels {
    jint kSecurityLevelHwSecureAll;
} gSecurityLevels;

struct OfflineLicenseState {
    jint kOfflineLicenseStateUsable;
    jint kOfflineLicenseStateInactive;
    jint kOfflineLicenseStateUnknown;
} gOfflineLicenseStates;


struct fields_t {
    jfieldID context;
@@ -740,6 +746,15 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
    GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_ALL", "I");
    gSecurityLevels.kSecurityLevelHwSecureAll = env->GetStaticIntField(clazz, field);

    GET_STATIC_FIELD_ID(field, clazz, "OFFLINE_LICENSE_USABLE", "I");
    gOfflineLicenseStates.kOfflineLicenseStateUsable = env->GetStaticIntField(clazz, field);
    GET_STATIC_FIELD_ID(field, clazz, "OFFLINE_LICENSE_INACTIVE", "I");
    gOfflineLicenseStates.kOfflineLicenseStateInactive = env->GetStaticIntField(clazz, field);
    GET_STATIC_FIELD_ID(field, clazz, "OFFLINE_LICENSE_STATE_UNKNOWN", "I");
    gOfflineLicenseStates.kOfflineLicenseStateUnknown = env->GetStaticIntField(clazz, field);

    GET_STATIC_FIELD_ID(field, clazz, "SECURITY_LEVEL_HW_SECURE_CRYPTO", "I");

    jmethodID getMaxSecurityLevel;
    GET_STATIC_METHOD_ID(getMaxSecurityLevel, clazz, "getMaxSecurityLevel", "()I");
    gSecurityLevels.kSecurityLevelMax = env->CallStaticIntMethod(clazz, getMaxSecurityLevel);
@@ -890,9 +905,7 @@ static jbyteArray android_media_MediaDrm_openSession(
        JNIEnv *env, jobject thiz, jint jlevel) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (drm == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "MediaDrm obj is null");
    if (!CheckDrm(env, drm)) {
        return NULL;
    }

@@ -1070,6 +1083,10 @@ static void android_media_MediaDrm_removeKeys(
    JNIEnv *env, jobject thiz, jbyteArray jkeysetId) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (!CheckDrm(env, drm)) {
        return;
    }

    if (jkeysetId == NULL) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "keySetId is null");
@@ -1231,9 +1248,7 @@ static jobject android_media_MediaDrm_getSecureStopIds(
    JNIEnv *env, jobject thiz) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (drm == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "MediaDrm obj is null");
    if (!CheckDrm(env, drm)) {
        return NULL;
    }

@@ -1286,9 +1301,7 @@ static void android_media_MediaDrm_removeSecureStop(
        JNIEnv *env, jobject thiz, jbyteArray ssid) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (drm == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "MediaDrm obj is null");
    if (!CheckDrm(env, drm)) {
        return;
    }

@@ -1437,6 +1450,65 @@ static jint android_media_MediaDrm_getSecurityLevel(JNIEnv *env,
    }
}

static jobject android_media_MediaDrm_getOfflineLicenseKeySetIds(
    JNIEnv *env, jobject thiz) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (!CheckDrm(env, drm)) {
        return NULL;
    }

    List<Vector<uint8_t> > keySetIds;

    status_t err = drm->getOfflineLicenseKeySetIds(keySetIds);

    if (throwExceptionAsNecessary(env, err, "Failed to get offline key set Ids")) {
        return NULL;
    }

    return ListOfVectorsToArrayListOfByteArray(env, keySetIds);
}

static void android_media_MediaDrm_removeOfflineLicense(
        JNIEnv *env, jobject thiz, jbyteArray keySetId) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (!CheckDrm(env, drm)) {
        return;
    }

    status_t err = drm->removeOfflineLicense(JByteArrayToVector(env, keySetId));

    throwExceptionAsNecessary(env, err, "Failed to remove offline license");
}

static jint android_media_MediaDrm_getOfflineLicenseState(JNIEnv *env,
        jobject thiz, jbyteArray jkeySetId) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (!CheckDrm(env, drm)) {
        return gOfflineLicenseStates.kOfflineLicenseStateUnknown;
    }

    Vector<uint8_t> keySetId(JByteArrayToVector(env, jkeySetId));

    DrmPlugin::OfflineLicenseState state = DrmPlugin::kOfflineLicenseStateUnknown;

    status_t err = drm->getOfflineLicenseState(keySetId, &state);

    if (throwExceptionAsNecessary(env, err, "Failed to get offline license state")) {
        return gOfflineLicenseStates.kOfflineLicenseStateUnknown;
    }

    switch(state) {
    case DrmPlugin::kOfflineLicenseStateUsable:
        return gOfflineLicenseStates.kOfflineLicenseStateUsable;
    case DrmPlugin::kOfflineLicenseStateInactive:
        return gOfflineLicenseStates.kOfflineLicenseStateInactive;
    default:
        return gOfflineLicenseStates.kOfflineLicenseStateUnknown;
    }
}

static jstring android_media_MediaDrm_getPropertyString(
    JNIEnv *env, jobject thiz, jstring jname) {
@@ -1718,9 +1790,8 @@ static jobject
android_media_MediaDrm_native_getMetrics(JNIEnv *env, jobject thiz)
{
    sp<IDrm> drm = GetDrm(env, thiz);
    if (drm == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "MediaDrm obj is null");

    if (!CheckDrm(env, drm)) {
        return NULL;
    }

@@ -1839,6 +1910,15 @@ static const JNINativeMethod gMethods[] = {
    { "getSecurityLevel", "([B)I",
      (void *)android_media_MediaDrm_getSecurityLevel },

    { "removeOfflineLicense", "([B)V",
      (void *)android_media_MediaDrm_removeOfflineLicense },

    { "getOfflineLicenseKeySetIds", "()Ljava/util/List;",
      (void *)android_media_MediaDrm_getOfflineLicenseKeySetIds },

    { "getOfflineLicenseState", "([B)I",
      (void *)android_media_MediaDrm_getOfflineLicenseState },

    { "getPropertyString", "(Ljava/lang/String;)Ljava/lang/String;",
      (void *)android_media_MediaDrm_getPropertyString },