/* * 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; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.app.ActivityThread; import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.media.metrics.LogSessionId; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.os.Parcel; import android.os.PersistableBundle; import android.util.Log; import dalvik.system.CloseGuard; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; /** * MediaDrm can be used to obtain keys for decrypting protected media streams, in * conjunction with {@link android.media.MediaCrypto}. The MediaDrm APIs * are designed to support the ISO/IEC 23001-7: Common Encryption standard, but * may also be used to implement other encryption schemes. *
* Encrypted content is prepared using an encryption server and stored in a content * library. The encrypted content is streamed or downloaded from the content library to * client devices via content servers. Licenses to view the content are obtained from * a License Server. *
*
* Keys are requested from the license server using a key request. The key * response is delivered to the client app, which provides the response to the * MediaDrm API. *
* A Provisioning server may be required to distribute device-unique credentials to * the devices. *
* Enforcing requirements related to the number of devices that may play content * simultaneously can be performed either through key renewal or using the secure * stop methods. *
* The following sequence diagram shows the interactions between the objects * involved while playing back encrypted content: *
*
* The app first constructs {@link android.media.MediaExtractor} and * {@link android.media.MediaCodec} objects. It accesses the DRM-scheme-identifying UUID, * typically from metadata in the content, and uses this UUID to construct an instance * of a MediaDrm object that is able to support the DRM scheme required by the content. * Crypto schemes are assigned 16 byte UUIDs. The method {@link #isCryptoSchemeSupported} * can be used to query if a given scheme is supported on the device. *
* The app calls {@link #openSession} to generate a sessionId that will uniquely identify * the session in subsequent interactions. The app next uses the MediaDrm object to * obtain a key request message and send it to the license server, then provide * the server's response to the MediaDrm object. *
* Once the app has a sessionId, it can construct a MediaCrypto object from the UUID and * sessionId. The MediaCrypto object is registered with the MediaCodec in the * {@link MediaCodec#configure} method to enable the codec to decrypt content. *
* When the app has constructed {@link android.media.MediaExtractor}, * {@link android.media.MediaCodec} and {@link android.media.MediaCrypto} objects, * it proceeds to pull samples from the extractor and queue them into the decoder. For * encrypted content, the samples returned from the extractor remain encrypted, they * are only decrypted when the samples are delivered to the decoder. *
* MediaDrm methods throw {@link android.media.MediaDrm.MediaDrmStateException} * when a method is called on a MediaDrm object that has had an unrecoverable failure * in the DRM plugin or security hardware. * {@link android.media.MediaDrm.MediaDrmStateException} extends * {@link java.lang.IllegalStateException} with the addition of a developer-readable * diagnostic information string associated with the exception. *
* In the event of a mediaserver process crash or restart while a MediaDrm object * is active, MediaDrm methods may throw {@link android.media.MediaDrmResetException}. * To recover, the app must release the MediaDrm object, then create and initialize * a new one. *
* As {@link android.media.MediaDrmResetException} and * {@link android.media.MediaDrm.MediaDrmStateException} both extend * {@link java.lang.IllegalStateException}, they should be in an earlier catch() * block than {@link java.lang.IllegalStateException} if handled separately. *
Applications should register for informational events in order
* to be informed of key state updates during playback or streaming.
* Registration for these events is done via a call to
* {@link #setOnEventListener}. In order to receive the respective
* callback associated with this listener, applications are required to create
* MediaDrm objects on a thread with its own Looper running (main UI
* thread by default has a Looper running).
*/
public final class MediaDrm implements AutoCloseable {
private static final String TAG = "MediaDrm";
private final AtomicBoolean mClosed = new AtomicBoolean();
private final CloseGuard mCloseGuard = CloseGuard.get();
private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
private long mNativeContext;
private final String mAppPackageName;
/**
* Specify no certificate type
*
* @hide - not part of the public API at this time
*/
public static final int CERTIFICATE_TYPE_NONE = 0;
/**
* Specify X.509 certificate type
*
* @hide - not part of the public API at this time
*/
public static final int CERTIFICATE_TYPE_X509 = 1;
/** @hide */
@IntDef({
CERTIFICATE_TYPE_NONE,
CERTIFICATE_TYPE_X509,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CertificateType {}
/**
* Query if the given scheme identified by its UUID is supported on
* this device.
* @param uuid The UUID of the crypto scheme.
*/
public static final boolean isCryptoSchemeSupported(@NonNull UUID uuid) {
return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null,
SECURITY_LEVEL_UNKNOWN);
}
/**
* Query if the given scheme identified by its UUID is supported on
* this device, and whether the DRM plugin is able to handle the
* media container format specified by mimeType.
* @param uuid The UUID of the crypto scheme.
* @param mimeType The MIME type of the media container, e.g. "video/mp4"
* or "video/webm"
*/
public static final boolean isCryptoSchemeSupported(
@NonNull UUID uuid, @NonNull String mimeType) {
return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid),
mimeType, SECURITY_LEVEL_UNKNOWN);
}
/**
* Query if the given scheme identified by its UUID is supported on
* this device, and whether the DRM plugin is able to handle the
* media container format specified by mimeType at the requested
* security level.
*
* Calling this method while the application is running on the physical Android device or a
* {@link android.companion.virtual.VirtualDevice} may lead to different results, based on
* the different DRM capabilities of the devices.
*
* @param uuid The UUID of the crypto scheme.
* @param mimeType The MIME type of the media container, e.g. "video/mp4"
* or "video/webm"
* @param securityLevel the security level requested
*/
public static final boolean isCryptoSchemeSupported(
@NonNull UUID uuid, @NonNull String mimeType, @SecurityLevel int securityLevel) {
return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType,
securityLevel);
}
/**
* @return list of crypto schemes (as {@link UUID}s) for which
* {@link #isCryptoSchemeSupported(UUID)} returns true; each {@link UUID}
* can be used as input to create {@link MediaDrm} objects via {@link #MediaDrm(UUID)}.
*/
public static final @NonNull List
* The description of each error code includes steps that may be taken to
* resolve the error condition. For some errors however, a recovery action
* cannot be predetermined. The description of those codes refers to a
* general strategy for handling the error condition programmatically, which
* is to try the following in listed order until successful:
*
* If the problem still persists after all the aforementioned steps, please
* report the failure to the {@link MediaDrm} plugin vendor along with the
* {@link LogMessage log messages} returned by {@link
* MediaDrm#getLogMessages()}, and a bugreport if possible.
*/
public final static class ErrorCodes {
private ErrorCodes() {}
/**
* ERROR_UNKNOWN is used where no other defined error code is applicable
* to the current failure.
*
* Please see the general error handling strategy for unexpected errors
* described in {@link ErrorCodes}.
*/
public static final int ERROR_UNKNOWN = 0;
/**
* The requested key was not found when trying to perform a decrypt
* operation.
*
* The operation can be retried after adding the correct decryption key.
*/
public static final int ERROR_NO_KEY = 1;
/**
* The key used for decryption is no longer valid due to license term
* expiration.
*
* The operation can be retried after updating the expired keys.
*/
public static final int ERROR_KEY_EXPIRED = 2;
/**
* A required crypto resource was not able to be allocated while
* attempting the requested operation.
*
* The operation can be retried if the app is able to release resources.
*/
public static final int ERROR_RESOURCE_BUSY = 3;
/**
* The output protection levels supported by the device are not
* sufficient to meet the requirements set by the content owner in the
* license policy.
*/
public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4;
/**
* Decryption was attempted on a session that is not opened, which could
* be due to a failure to open the session, closing the session
* prematurely, the session being reclaimed by the resource manager, or
* a non-existent session id.
*/
public static final int ERROR_SESSION_NOT_OPENED = 5;
/**
* An operation was attempted that could not be supported by the crypto
* system of the device in its current configuration.
*
* This may occur when the license policy requires device security
* features that aren't supported by the device, or due to an internal
* error in the crypto system that prevents the specified security
* policy from being met.
*/
public static final int ERROR_UNSUPPORTED_OPERATION = 6;
/**
* The security level of the device is not sufficient to meet the
* requirements set by the content owner in the license policy.
*/
public static final int ERROR_INSUFFICIENT_SECURITY = 7;
/**
* The video frame being decrypted exceeds the size of the device's
* protected output buffers.
*
* When encountering this error the app should try playing content
* of a lower resolution or skipping the problematic frame.
*/
public static final int ERROR_FRAME_TOO_LARGE = 8;
/**
* The session state has been invalidated. This can occur on devices
* that are not capable of retaining crypto session state across device
* suspend/resume.
*
* The session must be closed and a new session opened to resume
* operation.
*/
public static final int ERROR_LOST_STATE = 9;
/**
* Certificate is malformed or is of the wrong type.
*
* Ensure the certificate provided by the app or returned from the
* license server is valid. Check with the {@link MediaDrm} plugin
* vendor for the expected certificate format.
*/
public static final int ERROR_CERTIFICATE_MALFORMED = 10;
/**
* Certificate has not been set.
*
* Ensure the certificate has been provided by the app. Check with the
* {@link MediaDrm} plugin vendor for the expected method to provide
* {@link MediaDrm} a certificate.
*/
public static final int ERROR_CERTIFICATE_MISSING = 11;
/**
* An error happened within the crypto library used by the drm plugin.
*/
public static final int ERROR_CRYPTO_LIBRARY = 12;
/**
* Unexpected error reported by the device OEM subsystem.
*
* Please see the general error handling strategy for unexpected errors
* described in {@link ErrorCodes}.
*/
public static final int ERROR_GENERIC_OEM = 13;
/**
* Unexpected internal failure in {@link MediaDrm}/{@link MediaCrypto}.
*
* Please see the general error handling strategy for unexpected errors
* described in {@link ErrorCodes}.
*/
public static final int ERROR_GENERIC_PLUGIN = 14;
/**
* The init data parameter passed to {@link MediaDrm#getKeyRequest} is
* empty or invalid.
*
* Init data is typically obtained from {@link
* MediaExtractor#getPsshInfo()} or {@link
* MediaExtractor#getDrmInitData()}. Check with the {@link MediaDrm}
* plugin vendor for the expected init data format.
*/
public static final int ERROR_INIT_DATA = 15;
/**
* Either the key was not loaded from the license before attempting the
* operation, or the key ID parameter provided by the app is incorrect.
*
* Ensure the proper keys are in the license, and check the key ID
* parameter provided by the app is correct. Check with the {@link
* MediaDrm} plugin vendor for the expected license format.
*/
public static final int ERROR_KEY_NOT_LOADED = 16;
/**
* The license response was empty, fields are missing or otherwise
* unable to be parsed or decrypted.
*
* Check for mistakes such as empty or overwritten buffers. Otherwise,
* check with the {@link MediaDrm} plugin vendor for the expected
* license format.
*/
public static final int ERROR_LICENSE_PARSE = 17;
/**
* The operation (e.g. to renew or persist a license) is prohibited by
* the license policy.
*
* Check the license policy configuration on the license server.
*/
public static final int ERROR_LICENSE_POLICY = 18;
/**
* Failed to generate a release request because a field in the offline
* license is empty or malformed.
*
* The license can't be released on the server, but the app may remove
* the offline license explicitly using {@link
* MediaDrm#removeOfflineLicense}.
*/
public static final int ERROR_LICENSE_RELEASE = 19;
/**
* The license server detected an error in the license request.
*
* Check for errors on the license server.
*/
public static final int ERROR_LICENSE_REQUEST_REJECTED = 20;
/**
* Failed to restore an offline license because a field in the offline
* license is empty or malformed.
*
* Try requesting the license again if the device is online.
*/
public static final int ERROR_LICENSE_RESTORE = 21;
/**
* Offline license is in an invalid state for the attempted operation.
*
* Check the sequence of API calls made that can affect offline license
* state. For example, this could happen when the app attempts to
* restore a license after it has been released.
*/
public static final int ERROR_LICENSE_STATE = 22;
/**
* Failure in the media framework.
*
* Try releasing media resources (e.g. {@link MediaCodec}, {@link
* MediaDrm}), and restarting playback.
*/
public static final int ERROR_MEDIA_FRAMEWORK = 23;
/**
* Error loading the provisioned certificate.
*
* Re-provisioning may resolve the problem; check with the {@link
* MediaDrm} plugin vendor for re-provisioning instructions. Otherwise,
* using a different security level may resolve the issue.
*/
public static final int ERROR_PROVISIONING_CERTIFICATE = 24;
/**
* Required steps were not performed before provisioning was attempted.
*
* Ask the {@link MediaDrm} plugin vendor for situations where this
* error may occur.
*/
public static final int ERROR_PROVISIONING_CONFIG = 25;
/**
* The provisioning response was empty, fields are missing or otherwise
* unable to be parsed.
*
* Check for mistakes such as empty or overwritten buffers. Otherwise,
* check with the {@link MediaDrm} plugin vendor for the expected
* provisioning response format.
*/
public static final int ERROR_PROVISIONING_PARSE = 26;
/**
* The provisioning server detected an error in the provisioning
* request.
*
* Check for errors on the provisioning server.
*/
public static final int ERROR_PROVISIONING_REQUEST_REJECTED = 27;
/**
* Provisioning failed in a way that is likely to succeed on a
* subsequent attempt.
*
* The app should retry the operation.
*/
public static final int ERROR_PROVISIONING_RETRY = 28;
/**
* This indicates that apps using MediaDrm sessions are
* temporarily exceeding the capacity of available crypto
* resources.
*
* The app should retry the operation later.
*/
public static final int ERROR_RESOURCE_CONTENTION = 29;
/**
* Failed to generate a secure stop request because a field in the
* stored license is empty or malformed.
*
* The secure stop can't be released on the server, but the app may
* remove it explicitly using {@link MediaDrm#removeSecureStop}.
*/
public static final int ERROR_SECURE_STOP_RELEASE = 30;
/**
* The plugin was unable to read data from the filesystem.
*
* Please see the general error handling strategy for unexpected errors
* described in {@link ErrorCodes}.
*/
public static final int ERROR_STORAGE_READ = 31;
/**
* The plugin was unable to write data to the filesystem.
*
* Please see the general error handling strategy for unexpected errors
* described in {@link ErrorCodes}.
*/
public static final int ERROR_STORAGE_WRITE = 32;
/**
* {@link MediaCodec#queueSecureInputBuffer} called with 0 subsamples.
*
* Check the {@link MediaCodec.CryptoInfo} object passed to {@link
* MediaCodec#queueSecureInputBuffer}.
*/
public static final int ERROR_ZERO_SUBSAMPLES = 33;
}
/** @hide */
@IntDef({
ErrorCodes.ERROR_NO_KEY,
ErrorCodes.ERROR_KEY_EXPIRED,
ErrorCodes.ERROR_RESOURCE_BUSY,
ErrorCodes.ERROR_INSUFFICIENT_OUTPUT_PROTECTION,
ErrorCodes.ERROR_SESSION_NOT_OPENED,
ErrorCodes.ERROR_UNSUPPORTED_OPERATION,
ErrorCodes.ERROR_INSUFFICIENT_SECURITY,
ErrorCodes.ERROR_FRAME_TOO_LARGE,
ErrorCodes.ERROR_LOST_STATE,
ErrorCodes.ERROR_CERTIFICATE_MALFORMED,
ErrorCodes.ERROR_CERTIFICATE_MISSING,
ErrorCodes.ERROR_CRYPTO_LIBRARY,
ErrorCodes.ERROR_GENERIC_OEM,
ErrorCodes.ERROR_GENERIC_PLUGIN,
ErrorCodes.ERROR_INIT_DATA,
ErrorCodes.ERROR_KEY_NOT_LOADED,
ErrorCodes.ERROR_LICENSE_PARSE,
ErrorCodes.ERROR_LICENSE_POLICY,
ErrorCodes.ERROR_LICENSE_RELEASE,
ErrorCodes.ERROR_LICENSE_REQUEST_REJECTED,
ErrorCodes.ERROR_LICENSE_RESTORE,
ErrorCodes.ERROR_LICENSE_STATE,
ErrorCodes.ERROR_MEDIA_FRAMEWORK,
ErrorCodes.ERROR_PROVISIONING_CERTIFICATE,
ErrorCodes.ERROR_PROVISIONING_CONFIG,
ErrorCodes.ERROR_PROVISIONING_PARSE,
ErrorCodes.ERROR_PROVISIONING_REQUEST_REJECTED,
ErrorCodes.ERROR_PROVISIONING_RETRY,
ErrorCodes.ERROR_SECURE_STOP_RELEASE,
ErrorCodes.ERROR_STORAGE_READ,
ErrorCodes.ERROR_STORAGE_WRITE,
ErrorCodes.ERROR_ZERO_SUBSAMPLES
})
@Retention(RetentionPolicy.SOURCE)
public @interface MediaDrmErrorCode {}
/**
* Thrown when a general failure occurs during a MediaDrm operation.
* Extends {@link IllegalStateException} with the addition of an error
* code that may be useful in diagnosing the failure.
*
* Please refer to {@link ErrorCodes} for the general error handling
* strategy and details about each possible return value from {@link
* MediaDrmStateException#getErrorCode()}.
*/
public static final class MediaDrmStateException extends java.lang.IllegalStateException
implements MediaDrmThrowable {
private final int mErrorCode, mVendorError, mOemError, mErrorContext;
private final String mDiagnosticInfo;
/**
* @hide
*/
public MediaDrmStateException(int errorCode, @Nullable String detailMessage) {
this(detailMessage, errorCode, 0, 0, 0);
}
/**
* @hide
*/
public MediaDrmStateException(String detailMessage, int errorCode,
int vendorError, int oemError, int errorContext) {
super(detailMessage);
mErrorCode = errorCode;
mVendorError = vendorError;
mOemError = oemError;
mErrorContext = errorContext;
// TODO get this from DRM session
final String sign = errorCode < 0 ? "neg_" : "";
mDiagnosticInfo =
"android.media.MediaDrm.error_" + sign + Math.abs(errorCode);
}
/**
* Returns error code associated with this {@link
* MediaDrmStateException}.
*
* Please refer to {@link ErrorCodes} for the general error handling
* strategy and details about each possible return value.
*
* @return an error code defined in {@link MediaDrm.ErrorCodes}.
*/
@MediaDrmErrorCode
public int getErrorCode() {
return mErrorCode;
}
@Override
public int getVendorError() {
return mVendorError;
}
@Override
public int getOemError() {
return mOemError;
}
@Override
public int getErrorContext() {
return mErrorContext;
}
/**
* Returns true if the {@link MediaDrmStateException} is a transient
* issue, perhaps due to resource constraints, and that the operation
* (e.g. provisioning) may succeed on a subsequent attempt.
*/
public boolean isTransient() {
return mErrorCode == ErrorCodes.ERROR_PROVISIONING_RETRY
|| mErrorCode == ErrorCodes.ERROR_RESOURCE_CONTENTION;
}
/**
* Retrieve a developer-readable diagnostic information string
* associated with the exception. Do not show this to end-users,
* since this string will not be localized or generally comprehensible
* to end-users.
*/
@NonNull
public String getDiagnosticInfo() {
return mDiagnosticInfo;
}
}
/**
* {@link SessionException} is a misnomer because it may occur in methods
* without a session context.
*
* A {@link SessionException} is most likely to be thrown when an operation
* failed in a way that is likely to succeed on a subsequent attempt; call
* {@link #isTransient()} to determine whether the app should retry the
* failing operation.
*/
public static final class SessionException extends RuntimeException
implements MediaDrmThrowable {
public SessionException(int errorCode, @Nullable String detailMessage) {
this(detailMessage, errorCode, 0, 0, 0);
}
/**
* @hide
*/
public SessionException(String detailMessage, int errorCode, int vendorError, int oemError,
int errorContext) {
super(detailMessage);
mErrorCode = errorCode;
mVendorError = vendorError;
mOemError = oemError;
mErrorContext = errorContext;
}
/**
* The SessionException has an unknown error code.
* @deprecated Unused.
*/
public static final int ERROR_UNKNOWN = 0;
/**
* This indicates that apps using MediaDrm sessions are
* temporarily exceeding the capacity of available crypto
* resources. The app should retry the operation later.
*
* @deprecated Please use {@link #isTransient()} instead of comparing
* the return value of {@link #getErrorCode()} against
* {@link SessionException#ERROR_RESOURCE_CONTENTION}.
*/
public static final int ERROR_RESOURCE_CONTENTION = 1;
/** @hide */
@IntDef({
ERROR_RESOURCE_CONTENTION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SessionErrorCode {}
/**
* Retrieve the error code associated with the SessionException
*
* @deprecated Please use {@link #isTransient()} instead of comparing
* the return value of {@link #getErrorCode()} against
* {@link SessionException#ERROR_RESOURCE_CONTENTION}.
*/
@SessionErrorCode
public int getErrorCode() {
return mErrorCode;
}
@Override
public int getVendorError() {
return mVendorError;
}
@Override
public int getOemError() {
return mOemError;
}
@Override
public int getErrorContext() {
return mErrorContext;
}
/**
* Returns true if the {@link SessionException} is a transient
* issue, perhaps due to resource constraints, and that the operation
* (e.g. provisioning, generating requests) may succeed on a subsequent
* attempt.
*/
public boolean isTransient() {
return mErrorCode == ERROR_RESOURCE_CONTENTION;
}
private final int mErrorCode, mVendorError, mOemError, mErrorContext;
}
/**
* Register a callback to be invoked when a session expiration update
* occurs. The app's OnExpirationUpdateListener will be notified
* when the expiration time of the keys in the session have changed.
* @param listener the callback that will be run, or {@code null} to unregister the
* previously registered callback.
* @param handler the handler on which the listener should be invoked, or
* {@code null} if the listener should be invoked on the calling thread's looper.
*/
public void setOnExpirationUpdateListener(
@Nullable OnExpirationUpdateListener listener, @Nullable Handler handler) {
setListenerWithHandler(EXPIRATION_UPDATE, handler, listener,
this::createOnExpirationUpdateListener);
}
/**
* Register a callback to be invoked when a session expiration update
* occurs.
*
* @see #setOnExpirationUpdateListener(OnExpirationUpdateListener, Handler)
*
* @param executor the executor through which the listener should be invoked
* @param listener the callback that will be run.
*/
public void setOnExpirationUpdateListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnExpirationUpdateListener listener) {
setListenerWithExecutor(EXPIRATION_UPDATE, executor, listener,
this::createOnExpirationUpdateListener);
}
/**
* Clear the {@link OnExpirationUpdateListener}.
*/
public void clearOnExpirationUpdateListener() {
clearGenericListener(EXPIRATION_UPDATE);
}
/**
* Interface definition for a callback to be invoked when a drm session
* expiration update occurs
*/
public interface OnExpirationUpdateListener
{
/**
* Called when a session expiration update occurs, to inform the app
* about the change in expiration time
*
* @param md the MediaDrm object on which the event occurred
* @param sessionId the DRM session ID on which the event occurred
* @param expirationTime the new expiration time for the keys in the session.
* The time is in milliseconds, relative to the Unix epoch. A time of
* 0 indicates that the keys never expire.
*/
void onExpirationUpdate(
@NonNull MediaDrm md, @NonNull byte[] sessionId, long expirationTime);
}
/**
* Register a callback to be invoked when the state of keys in a session
* change, e.g. when a license update occurs or when a license expires.
*
* @param listener the callback that will be run when key status changes, or
* {@code null} to unregister the previously registered callback.
* @param handler the handler on which the listener should be invoked, or
* null if the listener should be invoked on the calling thread's looper.
*/
public void setOnKeyStatusChangeListener(
@Nullable OnKeyStatusChangeListener listener, @Nullable Handler handler) {
setListenerWithHandler(KEY_STATUS_CHANGE, handler, listener,
this::createOnKeyStatusChangeListener);
}
/**
* Register a callback to be invoked when the state of keys in a session
* change.
*
* @see #setOnKeyStatusChangeListener(OnKeyStatusChangeListener, Handler)
*
* @param listener the callback that will be run when key status changes.
* @param executor the executor on which the listener should be invoked.
*/
public void setOnKeyStatusChangeListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnKeyStatusChangeListener listener) {
setListenerWithExecutor(KEY_STATUS_CHANGE, executor, listener,
this::createOnKeyStatusChangeListener);
}
/**
* Clear the {@link OnKeyStatusChangeListener}.
*/
public void clearOnKeyStatusChangeListener() {
clearGenericListener(KEY_STATUS_CHANGE);
}
/**
* Interface definition for a callback to be invoked when the keys in a drm
* session change states.
*/
public interface OnKeyStatusChangeListener
{
/**
* Called when the keys in a session change status, such as when the license
* is renewed or expires.
*
* @param md the MediaDrm object on which the event occurred
* @param sessionId the DRM session ID on which the event occurred
* @param keyInformation a list of {@link MediaDrm.KeyStatus}
* instances indicating the status for each key in the session
* @param hasNewUsableKey indicates if a key has been added that is usable,
* which may trigger an attempt to resume playback on the media stream
* if it is currently blocked waiting for a key.
*/
void onKeyStatusChange(
@NonNull MediaDrm md, @NonNull byte[] sessionId,
@NonNull List
* getKeyRequest() is used to obtain an opaque key request byte array that is
* delivered to the license server. The opaque key request byte array is returned
* in KeyRequest.data. The recommended URL to deliver the key request to is
* returned in KeyRequest.defaultUrl.
*
* After the app has received the key request response from the server,
* it should deliver to the response to the MediaDrm instance using the method
* {@link #provideKeyResponse}.
*
* @param scope may be a sessionId or a keySetId, depending on the specified keyType.
* When the keyType is KEY_TYPE_STREAMING or KEY_TYPE_OFFLINE,
* scope should be set to the sessionId the keys will be provided to. When the keyType
* is KEY_TYPE_RELEASE, scope should be set to the keySetId of the keys
* being released. Releasing keys from a device invalidates them for all sessions.
* @param init container-specific data, its meaning is interpreted based on the
* mime type provided in the mimeType parameter. It could contain, for example,
* the content ID, key ID or other data obtained from the content metadata that is
* required in generating the key request. May be null when keyType is
* KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the first key
* request for the session.
* @param mimeType identifies the mime type of the content. May be null if the
* keyType is KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the
* first key request for the session.
* @param keyType specifes the type of the request. The request may be to acquire
* keys for streaming or offline content, or to release previously acquired
* keys, which are identified by a keySetId.
* @param optionalParameters are included in the key request message to
* allow a client application to provide additional message parameters to the server.
* This may be {@code null} if no additional parameters are to be sent.
* @throws NotProvisionedException if reprovisioning is needed, due to a
* problem with the certifcate
*/
@NonNull
public KeyRequest getKeyRequest(
@NonNull byte[] scope, @Nullable byte[] init,
@Nullable String mimeType, @KeyType int keyType,
@Nullable HashMap
* 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
* 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 is usable, the keys may be used for decryption.
*/
public static final int OFFLINE_LICENSE_STATE_USABLE = 1;
/**
* Offline license is released, 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_STATE_RELEASED = 2;
/** @hide */
@IntDef({
OFFLINE_LICENSE_STATE_UNKNOWN,
OFFLINE_LICENSE_STATE_USABLE,
OFFLINE_LICENSE_STATE_RELEASED,
})
@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
* @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
* the lifetime of content decryption keys in MediaDrm sessions.
*
* A secure stop is written to secure persistent memory when keys are loaded
* into a MediaDrm session. The secure stop state indicates that the keys
* are available for use. When playback completes and the keys are removed
* or the session is destroyed, the secure stop state is updated to indicate
* that keys are no longer usable.
*
* After playback, the app can query the secure stop and send it in a
* message to the license server confirming that the keys are no longer
* active. The license server returns a secure stop release response
* message to the app which then deletes the secure stop from persistent
* memory using {@link #releaseSecureStops}.
*
* Each secure stop has a unique ID that can be used to identify it during
* enumeration, access and removal.
*
* @return a list of all secure stops from secure persistent memory
* @deprecated This method is deprecated and may be removed in a future
* release. Secure stops are a way to enforce limits on the number of
* concurrent streams per subscriber across devices. They provide secure
* monitoring of the lifetime of content decryption keys in MediaDrm
* sessions. Limits on concurrent streams may also be enforced by
* periodically renewing licenses. This can be achieved by calling
* {@link #getKeyRequest} to initiate a renewal. MediaDrm users should
* transition away from secure stops to periodic renewals.
*/
@NonNull
public native List
* This method should only be used for informational purposes, not for
* enforcing compliance with HDCP requirements. Trusted enforcement of
* HDCP policies must be handled by the DRM system.
*
* @return the connected HDCP level
*/
@HdcpLevel
public native int getConnectedHdcpLevel();
/**
* Return the maximum supported HDCP level. The maximum HDCP level is a
* constant for a given device, it does not depend on downstream receivers
* that may be connected. If multiple HDCP-capable interfaces are present,
* it indicates the highest of the maximum HDCP levels of all interfaces.
*
* @return the maximum supported HDCP level
*/
@HdcpLevel
public native int getMaxHdcpLevel();
/**
* Return the number of MediaDrm sessions that are currently opened
* simultaneously among all MediaDrm instances for the active DRM scheme.
* @return the number of open sessions.
*/
public native int getOpenSessionCount();
/**
* Return the maximum number of MediaDrm sessions that may be opened
* simultaneosly among all MediaDrm instances for the active DRM
* scheme. The maximum number of sessions is not affected by any
* sessions that may have already been opened.
* @return maximum sessions.
*/
public native int getMaxSessionCount();
/**
* Security level indicates the robustness of the device's DRM
* implementation.
*
* @deprecated Not of any use for application development;
* please note that the related integer constants remain supported:
* {@link #SECURITY_LEVEL_UNKNOWN},
* {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO},
* {@link #SECURITY_LEVEL_SW_SECURE_DECODE},
* {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO},
* {@link #SECURITY_LEVEL_HW_SECURE_DECODE},
* {@link #SECURITY_LEVEL_HW_SECURE_ALL}
*
* @removed mistakenly exposed previously
*/
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@IntDef({SECURITY_LEVEL_UNKNOWN, SECURITY_LEVEL_SW_SECURE_CRYPTO,
SECURITY_LEVEL_SW_SECURE_DECODE, SECURITY_LEVEL_HW_SECURE_CRYPTO,
SECURITY_LEVEL_HW_SECURE_DECODE, SECURITY_LEVEL_HW_SECURE_ALL})
public @interface SecurityLevel {}
/**
* The DRM plugin did not report a security level, or an error occurred
* accessing it
*/
public static final int SECURITY_LEVEL_UNKNOWN = 0;
/**
* DRM key management uses software-based whitebox crypto.
*/
public static final int SECURITY_LEVEL_SW_SECURE_CRYPTO = 1;
/**
* DRM key management and decoding use software-based whitebox crypto.
*/
public static final int SECURITY_LEVEL_SW_SECURE_DECODE = 2;
/**
* DRM key management and crypto operations are performed within a hardware
* backed trusted execution environment.
*/
public static final int SECURITY_LEVEL_HW_SECURE_CRYPTO = 3;
/**
* DRM key management, crypto operations and decoding of content are
* performed within a hardware backed trusted execution environment.
*/
public static final int SECURITY_LEVEL_HW_SECURE_DECODE = 4;
/**
* DRM key management, crypto operations, decoding of content and all
* handling of the media (compressed and uncompressed) is handled within a
* hardware backed trusted execution environment.
*/
public static final int SECURITY_LEVEL_HW_SECURE_ALL = 5;
/**
* Indicates that the maximum security level supported by the device should
* be used when opening a session. This is the default security level
* selected when a session is opened.
* @hide
*/
public static final int SECURITY_LEVEL_MAX = 6;
/**
* Returns a value that may be passed as a parameter to {@link #openSession(int)}
* requesting that the session be opened at the maximum security level of
* the device.
*
* This security level is only valid for the application running on the physical Android
* device (e.g. {@link android.content.Context#DEVICE_ID_DEFAULT}). While running on a
* {@link android.companion.virtual.VirtualDevice} the maximum supported security level
* might be different.
*/
public static final int getMaxSecurityLevel() {
return SECURITY_LEVEL_MAX;
}
/**
* Return the current security level of a session. A session has an initial
* security level determined by the robustness of the DRM system's
* implementation on the device. The security level may be changed at the
* time a session is opened using {@link #openSession}.
* @param sessionId the session to query.
*
* @return the security level of the session
*/
@SecurityLevel
public native int getSecurityLevel(@NonNull byte[] sessionId);
/**
* String property name: identifies the maker of the DRM plugin
*/
public static final String PROPERTY_VENDOR = "vendor";
/**
* String property name: identifies the version of the DRM plugin
*/
public static final String PROPERTY_VERSION = "version";
/**
* String property name: describes the DRM plugin
*/
public static final String PROPERTY_DESCRIPTION = "description";
/**
* String property name: a comma-separated list of cipher and mac algorithms
* supported by CryptoSession. The list may be empty if the DRM
* plugin does not support CryptoSession operations.
*/
public static final String PROPERTY_ALGORITHMS = "algorithms";
/** @hide */
@StringDef(prefix = { "PROPERTY_" }, value = {
PROPERTY_VENDOR,
PROPERTY_VERSION,
PROPERTY_DESCRIPTION,
PROPERTY_ALGORITHMS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface StringProperty {}
/**
* Read a MediaDrm String property value, given the property name string.
*
* Standard fields names are:
* {@link #PROPERTY_VENDOR}, {@link #PROPERTY_VERSION},
* {@link #PROPERTY_DESCRIPTION}, {@link #PROPERTY_ALGORITHMS}
*/
@NonNull
public native String getPropertyString(@NonNull String propertyName);
/**
* Set a MediaDrm String property value, given the property name string
* and new value for the property.
*/
public native void setPropertyString(@NonNull String propertyName,
@NonNull String value);
/**
* Byte array property name: the device unique identifier is established during
* device provisioning and provides a means of uniquely identifying each device.
*/
public static final String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId";
/** @hide */
@StringDef(prefix = { "PROPERTY_" }, value = {
PROPERTY_DEVICE_UNIQUE_ID,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ArrayProperty {}
/**
* Read a MediaDrm byte array property value, given the property name string.
*
* Standard fields names are {@link #PROPERTY_DEVICE_UNIQUE_ID}
*/
@NonNull
public native byte[] getPropertyByteArray(String propertyName);
/**
* Set a MediaDrm byte array property value, given the property name string
* and new value for the property.
*/
public native void setPropertyByteArray(
@NonNull String propertyName, @NonNull byte[] value);
private static final native void setCipherAlgorithmNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm);
private static final native void setMacAlgorithmNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm);
@NonNull
private static final native byte[] encryptNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] input, @NonNull byte[] iv);
@NonNull
private static final native byte[] decryptNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] input, @NonNull byte[] iv);
@NonNull
private static final native byte[] signNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] message);
private static final native boolean verifyNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] message, @NonNull byte[] signature);
/**
* Return Metrics data about the current MediaDrm instance.
*
* @return a {@link PersistableBundle} containing the set of attributes and values
* available for this instance of MediaDrm.
* The attributes are described in {@link MetricsConstants}.
*
* Additional vendor-specific fields may also be present in
* the return value.
*/
public PersistableBundle getMetrics() {
PersistableBundle bundle = getMetricsNative();
return bundle;
}
private native PersistableBundle getMetricsNative();
/**
* In addition to supporting decryption of DASH Common Encrypted Media, the
* MediaDrm APIs provide the ability to securely deliver session keys from
* an operator's session key server to a client device, based on the factory-installed
* root of trust, and then perform encrypt, decrypt, sign and verify operations
* with the session key on arbitrary user data.
*
* The CryptoSession class implements generic encrypt/decrypt/sign/verify methods
* based on the established session keys. These keys are exchanged using the
* getKeyRequest/provideKeyResponse methods.
*
* Applications of this capability could include securing various types of
* purchased or private content, such as applications, books and other media,
* photos or media delivery protocols.
*
* Operators can create session key servers that are functionally similar to a
* license key server, except that instead of receiving license key requests and
* providing encrypted content keys which are used specifically to decrypt A/V media
* content, the session key server receives session key requests and provides
* encrypted session keys which can be used for general purpose crypto operations.
*
* A CryptoSession is obtained using {@link #getCryptoSession}
*/
public final class CryptoSession {
private byte[] mSessionId;
CryptoSession(@NonNull byte[] sessionId,
@NonNull String cipherAlgorithm,
@NonNull String macAlgorithm)
{
mSessionId = sessionId;
setCipherAlgorithmNative(MediaDrm.this, sessionId, cipherAlgorithm);
setMacAlgorithmNative(MediaDrm.this, sessionId, macAlgorithm);
}
/**
* Encrypt data using the CryptoSession's cipher algorithm
*
* @param keyid specifies which key to use
* @param input the data to encrypt
* @param iv the initialization vector to use for the cipher
*/
@NonNull
public byte[] encrypt(
@NonNull byte[] keyid, @NonNull byte[] input, @NonNull byte[] iv) {
return encryptNative(MediaDrm.this, mSessionId, keyid, input, iv);
}
/**
* Decrypt data using the CryptoSessions's cipher algorithm
*
* @param keyid specifies which key to use
* @param input the data to encrypt
* @param iv the initialization vector to use for the cipher
*/
@NonNull
public byte[] decrypt(
@NonNull byte[] keyid, @NonNull byte[] input, @NonNull byte[] iv) {
return decryptNative(MediaDrm.this, mSessionId, keyid, input, iv);
}
/**
* Sign data using the CryptoSessions's mac algorithm.
*
* @param keyid specifies which key to use
* @param message the data for which a signature is to be computed
*/
@NonNull
public byte[] sign(@NonNull byte[] keyid, @NonNull byte[] message) {
return signNative(MediaDrm.this, mSessionId, keyid, message);
}
/**
* Verify a signature using the CryptoSessions's mac algorithm. Return true
* if the signatures match, false if they do no.
*
* @param keyid specifies which key to use
* @param message the data to verify
* @param signature the reference signature which will be compared with the
* computed signature
*/
public boolean verify(
@NonNull byte[] keyid, @NonNull byte[] message, @NonNull byte[] signature) {
return verifyNative(MediaDrm.this, mSessionId, keyid, message, signature);
}
};
/**
* Obtain a CryptoSession object which can be used to encrypt, decrypt,
* sign and verify messages or data using the session keys established
* for the session using methods {@link #getKeyRequest} and
* {@link #provideKeyResponse} using a session key server.
*
* @param sessionId the session ID for the session containing keys
* to be used for encrypt, decrypt, sign and/or verify
* @param cipherAlgorithm the algorithm to use for encryption and
* decryption ciphers. The algorithm string conforms to JCA Standard
* Names for Cipher Transforms and is case insensitive. For example
* "AES/CBC/NoPadding".
* @param macAlgorithm the algorithm to use for sign and verify
* The algorithm string conforms to JCA Standard Names for Mac
* Algorithms and is case insensitive. For example "HmacSHA256".
*
* The list of supported algorithms for a DRM plugin can be obtained
* using the method {@link #getPropertyString} with the property name
* "algorithms".
*/
public CryptoSession getCryptoSession(
@NonNull byte[] sessionId,
@NonNull String cipherAlgorithm, @NonNull String macAlgorithm)
{
return new CryptoSession(sessionId, cipherAlgorithm, macAlgorithm);
}
/**
* Contains the opaque data an app uses to request a certificate from a provisioning
* server
*
* @hide - not part of the public API at this time
*/
public static final class CertificateRequest {
private byte[] mData;
private String mDefaultUrl;
CertificateRequest(@NonNull byte[] data, @NonNull String defaultUrl) {
mData = data;
mDefaultUrl = defaultUrl;
}
/**
* Get the opaque message data
*/
@NonNull
@UnsupportedAppUsage
public byte[] getData() { return mData; }
/**
* Get the default URL to use when sending the certificate request
* message to a server, if known. The app may prefer to use a different
* certificate server URL obtained from other sources.
*/
@NonNull
@UnsupportedAppUsage
public String getDefaultUrl() { return mDefaultUrl; }
}
/**
* Generate a certificate request, specifying the certificate type
* and authority. The response received should be passed to
* provideCertificateResponse.
*
* @param certType Specifies the certificate type.
*
* @param certAuthority is passed to the certificate server to specify
* the chain of authority.
*
* @hide - not part of the public API at this time
*/
@NonNull
@UnsupportedAppUsage
public CertificateRequest getCertificateRequest(
@CertificateType int certType, @NonNull String certAuthority)
{
ProvisionRequest provisionRequest = getProvisionRequestNative(certType, certAuthority);
return new CertificateRequest(provisionRequest.getData(),
provisionRequest.getDefaultUrl());
}
/**
* Contains the wrapped private key and public certificate data associated
* with a certificate.
*
* @hide - not part of the public API at this time
*/
public static final class Certificate {
Certificate() {}
/**
* Get the wrapped private key data
*/
@NonNull
@UnsupportedAppUsage
public byte[] getWrappedPrivateKey() {
if (mWrappedKey == null) {
// this should never happen as mWrappedKey is initialized in
// JNI after construction of the KeyRequest object. The check
// is needed here to guarantee @NonNull annotation.
throw new RuntimeException("Certificate is not initialized");
}
return mWrappedKey;
}
/**
* Get the PEM-encoded certificate chain
*/
@NonNull
@UnsupportedAppUsage
public byte[] getContent() {
if (mCertificateData == null) {
// this should never happen as mCertificateData is initialized in
// JNI after construction of the KeyRequest object. The check
// is needed here to guarantee @NonNull annotation.
throw new RuntimeException("Certificate is not initialized");
}
return mCertificateData;
}
private byte[] mWrappedKey;
private byte[] mCertificateData;
}
/**
* Process a response from the certificate server. The response
* is obtained from an HTTP Post to the url provided by getCertificateRequest.
*
* The public X509 certificate chain and wrapped private key are returned
* in the returned Certificate objec. The certificate chain is in PEM format.
* The wrapped private key should be stored in application private
* storage, and used when invoking the signRSA method.
*
* @param response the opaque certificate response byte array to provide to the
* MediaDrm instance.
*
* @throws DeniedByServerException if the response indicates that the
* server rejected the request
*
* @hide - not part of the public API at this time
*/
@NonNull
@UnsupportedAppUsage
public Certificate provideCertificateResponse(@NonNull byte[] response)
throws DeniedByServerException {
return provideProvisionResponseNative(response);
}
@NonNull
private static final native byte[] signRSANative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull String algorithm, @NonNull byte[] wrappedKey, @NonNull byte[] message);
/**
* Sign data using an RSA key
*
* @param sessionId a sessionId obtained from openSession on the MediaDrm object
* @param algorithm the signing algorithm to use, e.g. "PKCS1-BlockType1"
* @param wrappedKey - the wrapped (encrypted) RSA private key obtained
* from provideCertificateResponse
* @param message the data for which a signature is to be computed
*
* @hide - not part of the public API at this time
*/
@NonNull
@UnsupportedAppUsage
public byte[] signRSA(
@NonNull byte[] sessionId, @NonNull String algorithm,
@NonNull byte[] wrappedKey, @NonNull byte[] message) {
return signRSANative(this, sessionId, algorithm, wrappedKey, message);
}
/**
* Query if the crypto scheme requires the use of a secure decoder
* to decode data of the given mime type at the default security level.
* The default security level is defined as the highest security level
* supported on the device.
*
* @param mime The mime type of the media data. Please use {@link
* #isCryptoSchemeSupported(UUID, String)} to query mime type support separately;
* for unsupported mime types the return value of {@link
* #requiresSecureDecoder(String)} is crypto scheme dependent.
*/
public boolean requiresSecureDecoder(@NonNull String mime) {
return requiresSecureDecoder(mime, getMaxSecurityLevel());
}
/**
* Query if the crypto scheme requires the use of a secure decoder
* to decode data of the given mime type at the given security level.
*
* @param mime The mime type of the media data. Please use {@link
* #isCryptoSchemeSupported(UUID, String, int)} to query mime type support
* separately; for unsupported mime types the return value of {@link
* #requiresSecureDecoder(String, int)} is crypto scheme dependent.
* @param level a security level between {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}
* and {@link #SECURITY_LEVEL_HW_SECURE_ALL}. Otherwise the special value
* {@link #getMaxSecurityLevel()} is also permitted;
* use {@link #getMaxSecurityLevel()} to indicate the maximum security level
* supported by the device.
* @throws IllegalArgumentException if the requested security level is none of the documented
* values for the parameter {@code level}.
*/
public native boolean requiresSecureDecoder(@NonNull String mime, @SecurityLevel int level);
@Override
protected void finalize() throws Throwable {
try {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
release();
} finally {
super.finalize();
}
}
/**
* Releases resources associated with the current session of
* MediaDrm. It is considered good practice to call this method when
* the {@link MediaDrm} object is no longer needed in your
* application. After this method is called, {@link MediaDrm} is no
* longer usable since it has lost all of its required resource.
*
* This method was added in API 28. In API versions 18 through 27, release()
* should be called instead. There is no need to do anything for API
* versions prior to 18.
*/
@Override
public void close() {
release();
}
/**
* @deprecated replaced by {@link #close()}.
*/
@Deprecated
public void release() {
mCloseGuard.close();
if (mClosed.compareAndSet(false, true)) {
native_release();
mPlaybackComponentMap.clear();
}
}
/** @hide */
public native final void native_release();
private static native final void native_init();
private native final void native_setup(Object mediadrm_this, byte[] uuid,
String appPackageName);
static {
System.loadLibrary("media_jni");
native_init();
}
/**
* Definitions for the metrics that are reported via the
* {@link #getMetrics} call.
*/
public final static class MetricsConstants
{
private MetricsConstants() {}
/**
* Key to extract the number of successful {@link #openSession} calls
* from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String OPEN_SESSION_OK_COUNT
= "drm.mediadrm.open_session.ok.count";
/**
* Key to extract the number of failed {@link #openSession} calls
* from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String OPEN_SESSION_ERROR_COUNT
= "drm.mediadrm.open_session.error.count";
/**
* Key to extract the list of error codes that were returned from
* {@link #openSession} calls. The key is used to lookup the list
* in the {@link PersistableBundle} returned by a {@link #getMetrics}
* call.
* The list is an array of Long values
* ({@link android.os.BaseBundle#getLongArray}).
*/
public static final String OPEN_SESSION_ERROR_LIST
= "drm.mediadrm.open_session.error.list";
/**
* Key to extract the number of successful {@link #closeSession} calls
* from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String CLOSE_SESSION_OK_COUNT
= "drm.mediadrm.close_session.ok.count";
/**
* Key to extract the number of failed {@link #closeSession} calls
* from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String CLOSE_SESSION_ERROR_COUNT
= "drm.mediadrm.close_session.error.count";
/**
* Key to extract the list of error codes that were returned from
* {@link #closeSession} calls. The key is used to lookup the list
* in the {@link PersistableBundle} returned by a {@link #getMetrics}
* call.
* The list is an array of Long values
* ({@link android.os.BaseBundle#getLongArray}).
*/
public static final String CLOSE_SESSION_ERROR_LIST
= "drm.mediadrm.close_session.error.list";
/**
* Key to extract the start times of sessions. Times are
* represented as milliseconds since epoch (1970-01-01T00:00:00Z).
* The start times are returned from the {@link PersistableBundle}
* from a {@link #getMetrics} call.
* The start times are returned as another {@link PersistableBundle}
* containing the session ids as keys and the start times as long
* values. Use {@link android.os.BaseBundle#keySet} to get the list of
* session ids, and then {@link android.os.BaseBundle#getLong} to get
* the start time for each session.
*/
public static final String SESSION_START_TIMES_MS
= "drm.mediadrm.session_start_times_ms";
/**
* Key to extract the end times of sessions. Times are
* represented as milliseconds since epoch (1970-01-01T00:00:00Z).
* The end times are returned from the {@link PersistableBundle}
* from a {@link #getMetrics} call.
* The end times are returned as another {@link PersistableBundle}
* containing the session ids as keys and the end times as long
* values. Use {@link android.os.BaseBundle#keySet} to get the list of
* session ids, and then {@link android.os.BaseBundle#getLong} to get
* the end time for each session.
*/
public static final String SESSION_END_TIMES_MS
= "drm.mediadrm.session_end_times_ms";
/**
* Key to extract the number of successful {@link #getKeyRequest} calls
* from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String GET_KEY_REQUEST_OK_COUNT
= "drm.mediadrm.get_key_request.ok.count";
/**
* Key to extract the number of failed {@link #getKeyRequest}
* calls from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String GET_KEY_REQUEST_ERROR_COUNT
= "drm.mediadrm.get_key_request.error.count";
/**
* Key to extract the list of error codes that were returned from
* {@link #getKeyRequest} calls. The key is used to lookup the list
* in the {@link PersistableBundle} returned by a {@link #getMetrics}
* call.
* The list is an array of Long values
* ({@link android.os.BaseBundle#getLongArray}).
*/
public static final String GET_KEY_REQUEST_ERROR_LIST
= "drm.mediadrm.get_key_request.error.list";
/**
* Key to extract the average time in microseconds of calls to
* {@link #getKeyRequest}. The value is retrieved from the
* {@link PersistableBundle} returned from {@link #getMetrics}.
* The time is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String GET_KEY_REQUEST_OK_TIME_MICROS
= "drm.mediadrm.get_key_request.ok.average_time_micros";
/**
* Key to extract the number of successful {@link #provideKeyResponse}
* calls from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String PROVIDE_KEY_RESPONSE_OK_COUNT
= "drm.mediadrm.provide_key_response.ok.count";
/**
* Key to extract the number of failed {@link #provideKeyResponse}
* calls from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String PROVIDE_KEY_RESPONSE_ERROR_COUNT
= "drm.mediadrm.provide_key_response.error.count";
/**
* Key to extract the list of error codes that were returned from
* {@link #provideKeyResponse} calls. The key is used to lookup the
* list in the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The list is an array of Long values
* ({@link android.os.BaseBundle#getLongArray}).
*/
public static final String PROVIDE_KEY_RESPONSE_ERROR_LIST
= "drm.mediadrm.provide_key_response.error.list";
/**
* Key to extract the average time in microseconds of calls to
* {@link #provideKeyResponse}. The valus is retrieved from the
* {@link PersistableBundle} returned from {@link #getMetrics}.
* The time is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String PROVIDE_KEY_RESPONSE_OK_TIME_MICROS
= "drm.mediadrm.provide_key_response.ok.average_time_micros";
/**
* Key to extract the number of successful {@link #getProvisionRequest}
* calls from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String GET_PROVISION_REQUEST_OK_COUNT
= "drm.mediadrm.get_provision_request.ok.count";
/**
* Key to extract the number of failed {@link #getProvisionRequest}
* calls from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String GET_PROVISION_REQUEST_ERROR_COUNT
= "drm.mediadrm.get_provision_request.error.count";
/**
* Key to extract the list of error codes that were returned from
* {@link #getProvisionRequest} calls. The key is used to lookup the
* list in the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The list is an array of Long values
* ({@link android.os.BaseBundle#getLongArray}).
*/
public static final String GET_PROVISION_REQUEST_ERROR_LIST
= "drm.mediadrm.get_provision_request.error.list";
/**
* Key to extract the number of successful
* {@link #provideProvisionResponse} calls from the
* {@link PersistableBundle} returned by a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String PROVIDE_PROVISION_RESPONSE_OK_COUNT
= "drm.mediadrm.provide_provision_response.ok.count";
/**
* Key to extract the number of failed
* {@link #provideProvisionResponse} calls from the
* {@link PersistableBundle} returned by a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String PROVIDE_PROVISION_RESPONSE_ERROR_COUNT
= "drm.mediadrm.provide_provision_response.error.count";
/**
* Key to extract the list of error codes that were returned from
* {@link #provideProvisionResponse} calls. The key is used to lookup
* the list in the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The list is an array of Long values
* ({@link android.os.BaseBundle#getLongArray}).
*/
public static final String PROVIDE_PROVISION_RESPONSE_ERROR_LIST
= "drm.mediadrm.provide_provision_response.error.list";
/**
* Key to extract the number of successful
* {@link #getPropertyByteArray} calls were made with the
* {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
* the value in the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String GET_DEVICE_UNIQUE_ID_OK_COUNT
= "drm.mediadrm.get_device_unique_id.ok.count";
/**
* Key to extract the number of failed
* {@link #getPropertyByteArray} calls were made with the
* {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
* the value in the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String GET_DEVICE_UNIQUE_ID_ERROR_COUNT
= "drm.mediadrm.get_device_unique_id.error.count";
/**
* Key to extract the list of error codes that were returned from
* {@link #getPropertyByteArray} calls with the
* {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
* the list in the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
* The list is an array of Long values
* ({@link android.os.BaseBundle#getLongArray}).
*/
public static final String GET_DEVICE_UNIQUE_ID_ERROR_LIST
= "drm.mediadrm.get_device_unique_id.error.list";
/**
* Key to extract the count of {@link KeyStatus#STATUS_EXPIRED} events
* that occured. The count is extracted from the
* {@link PersistableBundle} returned from a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String KEY_STATUS_EXPIRED_COUNT
= "drm.mediadrm.key_status.EXPIRED.count";
/**
* Key to extract the count of {@link KeyStatus#STATUS_INTERNAL_ERROR}
* events that occured. The count is extracted from the
* {@link PersistableBundle} returned from a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String KEY_STATUS_INTERNAL_ERROR_COUNT
= "drm.mediadrm.key_status.INTERNAL_ERROR.count";
/**
* Key to extract the count of
* {@link KeyStatus#STATUS_OUTPUT_NOT_ALLOWED} events that occured.
* The count is extracted from the
* {@link PersistableBundle} returned from a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String KEY_STATUS_OUTPUT_NOT_ALLOWED_COUNT
= "drm.mediadrm.key_status_change.OUTPUT_NOT_ALLOWED.count";
/**
* Key to extract the count of {@link KeyStatus#STATUS_PENDING}
* events that occured. The count is extracted from the
* {@link PersistableBundle} returned from a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String KEY_STATUS_PENDING_COUNT
= "drm.mediadrm.key_status_change.PENDING.count";
/**
* Key to extract the count of {@link KeyStatus#STATUS_USABLE}
* events that occured. The count is extracted from the
* {@link PersistableBundle} returned from a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String KEY_STATUS_USABLE_COUNT
= "drm.mediadrm.key_status_change.USABLE.count";
/**
* Key to extract the count of {@link OnEventListener#onEvent}
* calls of type PROVISION_REQUIRED occured. The count is
* extracted from the {@link PersistableBundle} returned from a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String EVENT_PROVISION_REQUIRED_COUNT
= "drm.mediadrm.event.PROVISION_REQUIRED.count";
/**
* Key to extract the count of {@link OnEventListener#onEvent}
* calls of type KEY_NEEDED occured. The count is
* extracted from the {@link PersistableBundle} returned from a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String EVENT_KEY_NEEDED_COUNT
= "drm.mediadrm.event.KEY_NEEDED.count";
/**
* Key to extract the count of {@link OnEventListener#onEvent}
* calls of type KEY_EXPIRED occured. The count is
* extracted from the {@link PersistableBundle} returned from a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String EVENT_KEY_EXPIRED_COUNT
= "drm.mediadrm.event.KEY_EXPIRED.count";
/**
* Key to extract the count of {@link OnEventListener#onEvent}
* calls of type VENDOR_DEFINED. The count is
* extracted from the {@link PersistableBundle} returned from a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String EVENT_VENDOR_DEFINED_COUNT
= "drm.mediadrm.event.VENDOR_DEFINED.count";
/**
* Key to extract the count of {@link OnEventListener#onEvent}
* calls of type SESSION_RECLAIMED. The count is
* extracted from the {@link PersistableBundle} returned from a
* {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String EVENT_SESSION_RECLAIMED_COUNT
= "drm.mediadrm.event.SESSION_RECLAIMED.count";
}
/**
* Obtain a {@link PlaybackComponent} associated with a DRM session.
* Call {@link PlaybackComponent#setLogSessionId(LogSessionId)} on
* the returned object to associate a playback session with the DRM session.
*
* @param sessionId a DRM session ID obtained from {@link #openSession()}
* @return a {@link PlaybackComponent} associated with the session,
* or {@code null} if the session is closed or does not exist.
* @see PlaybackComponent
*/
@Nullable
public PlaybackComponent getPlaybackComponent(@NonNull byte[] sessionId) {
if (sessionId == null) {
throw new IllegalArgumentException("sessionId is null");
}
return mPlaybackComponentMap.get(ByteBuffer.wrap(sessionId));
}
private native void setPlaybackId(byte[] sessionId, String logSessionId);
/** This class contains the Drm session ID and log session ID */
public final class PlaybackComponent {
private final byte[] mSessionId;
@NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
/** @hide */
public PlaybackComponent(byte[] sessionId) {
mSessionId = sessionId;
}
/**
* Sets the {@link LogSessionId}.
*
* The implementation of this method varies by DRM provider; Please refer
* to your DRM provider documentation for more details on this method.
*
* @throws UnsupportedOperationException when the vendor plugin does not
* implement this method
*/
public void setLogSessionId(@NonNull LogSessionId logSessionId) {
Objects.requireNonNull(logSessionId);
if (logSessionId.getStringId() == null) {
throw new IllegalArgumentException("playbackId is null");
}
MediaDrm.this.setPlaybackId(mSessionId, logSessionId.getStringId());
mLogSessionId = logSessionId;
}
/**
* Returns the {@link LogSessionId}.
*/
@NonNull public LogSessionId getLogSessionId() {
return mLogSessionId;
}
}
/**
* Returns recent {@link LogMessage LogMessages} associated with this {@link MediaDrm}
* instance.
*/
@NonNull
public native List
* Possible priority constants are defined in {@link Log}, e.g.:
*
*
*
*
*/
@Log.Level
public final int getPriority() { return priority; }
/**
* Description of the recorded event.
*/
@NonNull
public final String getMessage() { return message; }
private LogMessage(long timestampMillis, int priority, String message) {
this.timestampMillis = timestampMillis;
if (priority < Log.VERBOSE || priority > Log.ASSERT) {
throw new IllegalArgumentException("invalid log priority " + priority);
}
this.priority = priority;
this.message = message;
}
private char logPriorityChar() {
switch (priority) {
case Log.VERBOSE:
return 'V';
case Log.DEBUG:
return 'D';
case Log.INFO:
return 'I';
case Log.WARN:
return 'W';
case Log.ERROR:
return 'E';
case Log.ASSERT:
return 'F';
default:
}
return 'U';
}
@Override
public String toString() {
return String.format("LogMessage{%s %c %s}",
Instant.ofEpochMilli(timestampMillis), logPriorityChar(), message);
}
}
}