/* * 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. *

*

MediaDrm Overview diagram

*

* 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: *

*

MediaDrm Overview diagram

*

* 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. *

* *

Callbacks

*

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 getSupportedCryptoSchemes(){ byte[] uuidBytes = getSupportedCryptoSchemesNative(); return getUUIDsFromByteArray(uuidBytes); } private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { long msb = uuid.getMostSignificantBits(); long lsb = uuid.getLeastSignificantBits(); byte[] uuidBytes = new byte[16]; for (int i = 0; i < 8; ++i) { uuidBytes[i] = (byte)(msb >>> (8 * (7 - i))); uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i))); } return uuidBytes; } private static final UUID getUUIDFromByteArray(@NonNull byte[] uuidBytes, int off) { long msb = 0; long lsb = 0; for (int i = 0; i < 8; ++i) { msb = (msb << 8) | (0xffl & uuidBytes[off + i]); lsb = (lsb << 8) | (0xffl & uuidBytes[off + i + 8]); } return new UUID(msb, lsb); } private static final List getUUIDsFromByteArray(@NonNull byte[] uuidBytes) { Set uuids = new LinkedHashSet<>(); for (int off = 0; off < uuidBytes.length; off+=16) { uuids.add(getUUIDFromByteArray(uuidBytes, off)); } return new ArrayList<>(uuids); } private static final native byte[] getSupportedCryptoSchemesNative(); private static final native boolean isCryptoSchemeSupportedNative( @NonNull byte[] uuid, @Nullable String mimeType, @SecurityLevel int securityLevel); private Handler createHandler() { Looper looper; Handler handler; if ((looper = Looper.myLooper()) != null) { handler = new Handler(looper); } else if ((looper = Looper.getMainLooper()) != null) { handler = new Handler(looper); } else { handler = null; } return handler; } /** * Instantiate a MediaDrm object * * @param uuid The UUID of the crypto scheme. * * @throws UnsupportedSchemeException if the device does not support the * specified scheme UUID */ public MediaDrm(@NonNull UUID uuid) throws UnsupportedSchemeException { /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ mAppPackageName = ActivityThread.currentOpPackageName(); native_setup(new WeakReference(this), getByteArrayFromUUID(uuid), mAppPackageName); mCloseGuard.open("release"); } /** * Error codes that may be returned from {@link * MediaDrmStateException#getErrorCode()} and {@link * MediaCodec.CryptoException#getErrorCode()} *

* 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: *

    *
  1. retry the operation
  2. *
  3. if the operation is related to a session, {@link * #closeSession(byte[]) close} the session, {@link #openSession() open} a * new session, and retry the operation
  4. *
  5. {@link #close() close} the {@link MediaDrm} instance and any other * related components such as the {@link MediaCodec codec} and retry * playback, or
  6. *
  7. try using a different configuration of the {@link MediaDrm} plugin, * such as a different {@link #openSession(int) security level}.
  8. *
*

* 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 keyInformation, boolean hasNewUsableKey); } /** * Register a callback to be invoked when session state has been * lost. This event can occur on devices that are not capable of * retaining crypto session state across device suspend/resume * cycles. When this event occurs, the session must be closed and * a new session opened to resume operation. * * @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 setOnSessionLostStateListener( @Nullable OnSessionLostStateListener listener, @Nullable Handler handler) { setListenerWithHandler(SESSION_LOST_STATE, handler, listener, this::createOnSessionLostStateListener); } /** * Register a callback to be invoked when session state has been * lost. * * @see #setOnSessionLostStateListener(OnSessionLostStateListener, Handler) * * @param listener the callback that will be run. * @param executor the executor on which the listener should be invoked. */ public void setOnSessionLostStateListener( @NonNull @CallbackExecutor Executor executor, @Nullable OnSessionLostStateListener listener) { setListenerWithExecutor(SESSION_LOST_STATE, executor, listener, this::createOnSessionLostStateListener); } /** * Clear the {@link OnSessionLostStateListener}. */ public void clearOnSessionLostStateListener() { clearGenericListener(SESSION_LOST_STATE); } /** * Interface definition for a callback to be invoked when the * session state has been lost and is now invalid */ public interface OnSessionLostStateListener { /** * Called when session state has lost state, to inform the app * about the condition so it can close the session and open a new * one to resume operation. * * @param md the MediaDrm object on which the event occurred * @param sessionId the DRM session ID on which the event occurred */ void onSessionLostState( @NonNull MediaDrm md, @NonNull byte[] sessionId); } /** * Defines the status of a key. * A KeyStatus for each key in a session is provided to the * {@link OnKeyStatusChangeListener#onKeyStatusChange} * listener. */ public static final class KeyStatus { private final byte[] mKeyId; private final int mStatusCode; /** * The key is currently usable to decrypt media data */ public static final int STATUS_USABLE = 0; /** * The key is no longer usable to decrypt media data because its * expiration time has passed. */ public static final int STATUS_EXPIRED = 1; /** * The key is not currently usable to decrypt media data because its * output requirements cannot currently be met. */ public static final int STATUS_OUTPUT_NOT_ALLOWED = 2; /** * The status of the key is not yet known and is being determined. * The status will be updated with the actual status when it has * been determined. */ public static final int STATUS_PENDING = 3; /** * The key is not currently usable to decrypt media data because of an * internal error in processing unrelated to input parameters. This error * is not actionable by an app. */ public static final int STATUS_INTERNAL_ERROR = 4; /** * The key is not yet usable to decrypt media because the start * time is in the future. The key will become usable when * its start time is reached. */ public static final int STATUS_USABLE_IN_FUTURE = 5; /** @hide */ @IntDef({ STATUS_USABLE, STATUS_EXPIRED, STATUS_OUTPUT_NOT_ALLOWED, STATUS_PENDING, STATUS_INTERNAL_ERROR, STATUS_USABLE_IN_FUTURE, }) @Retention(RetentionPolicy.SOURCE) public @interface KeyStatusCode {} KeyStatus(@NonNull byte[] keyId, @KeyStatusCode int statusCode) { mKeyId = keyId; mStatusCode = statusCode; } /** * Returns the status code for the key */ @KeyStatusCode public int getStatusCode() { return mStatusCode; } /** * Returns the id for the key */ @NonNull public byte[] getKeyId() { return mKeyId; } } /** * Register a callback to be invoked when an event occurs * * @see #setOnEventListener(OnEventListener, Handler) * * @param listener the callback that will be run. Use {@code null} to * stop receiving event callbacks. */ public void setOnEventListener(@Nullable OnEventListener listener) { setOnEventListener(listener, null); } /** * Register a callback to be invoked when an event occurs * * @param listener the callback that will be run. Use {@code null} to * stop receiving event callbacks. * @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 setOnEventListener(@Nullable OnEventListener listener, @Nullable Handler handler) { setListenerWithHandler(DRM_EVENT, handler, listener, this::createOnEventListener); } /** * Register a callback to be invoked when an event occurs * * @see #setOnEventListener(OnEventListener) * * @param executor the executor through which the listener should be invoked * @param listener the callback that will be run. */ public void setOnEventListener(@NonNull @CallbackExecutor Executor executor, @NonNull OnEventListener listener) { setListenerWithExecutor(DRM_EVENT, executor, listener, this::createOnEventListener); } /** * Clear the {@link OnEventListener}. */ public void clearOnEventListener() { clearGenericListener(DRM_EVENT); } /** * Interface definition for a callback to be invoked when a drm event * occurs */ public interface OnEventListener { /** * Called when an event occurs that requires the app to be notified * * @param md the MediaDrm object on which the event occurred * @param sessionId the DRM session ID on which the event occurred, * or {@code null} if there is no session ID associated with the event. * @param event indicates the event type * @param extra an secondary error code * @param data optional byte array of data that may be associated with the event */ void onEvent( @NonNull MediaDrm md, @Nullable byte[] sessionId, @DrmEvent int event, int extra, @Nullable byte[] data); } /** * This event type indicates that the app needs to request a certificate from * the provisioning server. The request message data is obtained using * {@link #getProvisionRequest} * * @deprecated Handle provisioning via {@link android.media.NotProvisionedException} * instead. */ public static final int EVENT_PROVISION_REQUIRED = 1; /** * This event type indicates that the app needs to request keys from a license * server. The request message data is obtained using {@link #getKeyRequest}. */ public static final int EVENT_KEY_REQUIRED = 2; /** * This event type indicates that the licensed usage duration for keys in a session * has expired. The keys are no longer valid. * @deprecated Use {@link OnKeyStatusChangeListener#onKeyStatusChange} * and check for {@link MediaDrm.KeyStatus#STATUS_EXPIRED} in the {@link MediaDrm.KeyStatus} * instead. */ public static final int EVENT_KEY_EXPIRED = 3; /** * This event may indicate some specific vendor-defined condition, see your * DRM provider documentation for details */ public static final int EVENT_VENDOR_DEFINED = 4; /** * This event indicates that a session opened by the app has been reclaimed by the resource * manager. */ public static final int EVENT_SESSION_RECLAIMED = 5; /** @hide */ @IntDef({ EVENT_PROVISION_REQUIRED, EVENT_KEY_REQUIRED, EVENT_KEY_EXPIRED, EVENT_VENDOR_DEFINED, EVENT_SESSION_RECLAIMED, }) @Retention(RetentionPolicy.SOURCE) public @interface DrmEvent {} private static final int DRM_EVENT = 200; private static final int EXPIRATION_UPDATE = 201; private static final int KEY_STATUS_CHANGE = 202; private static final int SESSION_LOST_STATE = 203; // Use ConcurrentMap to support concurrent read/write to listener settings. // ListenerWithExecutor is immutable so we shouldn't need further locks. private final Map mListenerMap = new ConcurrentHashMap<>(); // called by old-style set*Listener APIs using Handlers; listener & handler are Nullable private void setListenerWithHandler(int what, Handler handler, T listener, Function> converter) { if (listener == null) { clearGenericListener(what); } else { handler = handler == null ? createHandler() : handler; final HandlerExecutor executor = new HandlerExecutor(handler); setGenericListener(what, executor, listener, converter); } } // called by new-style set*Listener APIs using Executors; listener & executor must be NonNull private void setListenerWithExecutor(int what, Executor executor, T listener, Function> converter) { if (executor == null || listener == null) { final String errMsg = String.format("executor %s listener %s", executor, listener); throw new IllegalArgumentException(errMsg); } setGenericListener(what, executor, listener, converter); } private void setGenericListener(int what, Executor executor, T listener, Function> converter) { mListenerMap.put(what, new ListenerWithExecutor(executor, converter.apply(listener))); } private void clearGenericListener(int what) { mListenerMap.remove(what); } private Consumer createOnEventListener(OnEventListener listener) { return args -> { byte[] sessionId = args.sessionId; if (sessionId.length == 0) { sessionId = null; } byte[] data = args.data; if (data != null && data.length == 0) { data = null; } Log.i(TAG, "Drm event (" + args.arg1 + "," + args.arg2 + ")"); listener.onEvent(this, sessionId, args.arg1, args.arg2, data); }; } private Consumer createOnKeyStatusChangeListener( OnKeyStatusChangeListener listener) { return args -> { byte[] sessionId = args.sessionId; if (sessionId.length > 0) { List keyStatusList = args.keyStatusList; boolean hasNewUsableKey = args.hasNewUsableKey; Log.i(TAG, "Drm key status changed"); listener.onKeyStatusChange(this, sessionId, keyStatusList, hasNewUsableKey); } }; } private Consumer createOnExpirationUpdateListener( OnExpirationUpdateListener listener) { return args -> { byte[] sessionId = args.sessionId; if (sessionId.length > 0) { long expirationTime = args.expirationTime; Log.i(TAG, "Drm key expiration update: " + expirationTime); listener.onExpirationUpdate(this, sessionId, expirationTime); } }; } private Consumer createOnSessionLostStateListener( OnSessionLostStateListener listener) { return args -> { byte[] sessionId = args.sessionId; Log.i(TAG, "Drm session lost state event: "); listener.onSessionLostState(this, sessionId); }; } private static class ListenerArgs { private final int arg1; private final int arg2; private final byte[] sessionId; private final byte[] data; private final long expirationTime; private final List keyStatusList; private final boolean hasNewUsableKey; public ListenerArgs( int arg1, int arg2, byte[] sessionId, byte[] data, long expirationTime, List keyStatusList, boolean hasNewUsableKey) { this.arg1 = arg1; this.arg2 = arg2; this.sessionId = sessionId; this.data = data; this.expirationTime = expirationTime; this.keyStatusList = keyStatusList; this.hasNewUsableKey = hasNewUsableKey; } } private static class ListenerWithExecutor { private final Consumer mConsumer; private final Executor mExecutor; public ListenerWithExecutor(Executor executor, Consumer consumer) { this.mExecutor = executor; this.mConsumer = consumer; } } /** * Parse a list of KeyStatus objects from an event parcel */ @NonNull private List keyStatusListFromParcel(@NonNull Parcel parcel) { int nelems = parcel.readInt(); List keyStatusList = new ArrayList(nelems); while (nelems-- > 0) { byte[] keyId = parcel.createByteArray(); int keyStatusCode = parcel.readInt(); keyStatusList.add(new KeyStatus(keyId, keyStatusCode)); } return keyStatusList; } /** * This method is called from native code when an event occurs. This method * just uses the EventHandler system to post the event back to the main app thread. * We use a weak reference to the original MediaPlayer object so that the native * code is safe from the object disappearing from underneath it. (This is * the cookie passed to native_setup().) */ private static void postEventFromNative(@NonNull Object mediadrm_ref, int what, int eventType, int extra, byte[] sessionId, byte[] data, long expirationTime, List keyStatusList, boolean hasNewUsableKey) { MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get(); if (md == null) { return; } switch (what) { case DRM_EVENT: case EXPIRATION_UPDATE: case KEY_STATUS_CHANGE: case SESSION_LOST_STATE: ListenerWithExecutor listener = md.mListenerMap.get(what); if (listener != null) { final Runnable command = () -> { if (md.mNativeContext == 0) { Log.w(TAG, "MediaDrm went away with unhandled events"); return; } ListenerArgs args = new ListenerArgs(eventType, extra, sessionId, data, expirationTime, keyStatusList, hasNewUsableKey); listener.mConsumer.accept(args); }; listener.mExecutor.execute(command); } break; default: Log.e(TAG, "Unknown message type " + what); break; } } /** * Open a new session with the MediaDrm object. A session ID is returned. * By default, sessions are opened at the native security level of the device. * * If the application is currently running on a {@link android.companion.virtual.VirtualDevice} * the security level will be adjusted accordingly to the maximum supported level for the * display. * * @throws NotProvisionedException if provisioning is needed * @throws ResourceBusyException if required resources are in use */ @NonNull public byte[] openSession() throws NotProvisionedException, ResourceBusyException { return openSession(getMaxSecurityLevel()); } /** * Open a new session at a requested security level. The security level * represents the robustness of the device's DRM implementation. By default, * sessions are opened at the native security level of the device. * Overriding the security level is necessary when the decrypted frames need * to be manipulated, such as for image compositing. The security level * parameter must be lower than the native level. Reducing the security * level will typically limit the content to lower resolutions, as * determined by the license policy. If the requested level is not * supported, the next lower supported security level will be set. The level * can be queried using {@link #getSecurityLevel}. A session * ID is returned. * * If the application is currently running on a {@link android.companion.virtual.VirtualDevice} * the security level will be adjusted accordingly to the maximum supported level for the * display. * * @param level the new security level * @throws NotProvisionedException if provisioning is needed * @throws ResourceBusyException if required resources are in use * @throws IllegalArgumentException if the requested security level is * higher than the native level or lower than the lowest supported level or * if the device does not support specifying the security level when opening * a session */ @NonNull public byte[] openSession(@SecurityLevel int level) throws NotProvisionedException, ResourceBusyException { byte[] sessionId = openSessionNative(level); mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponent(sessionId)); return sessionId; } @NonNull private native byte[] openSessionNative(int level) throws NotProvisionedException, ResourceBusyException; /** * Close a session on the MediaDrm object that was previously opened * with {@link #openSession}. */ public void closeSession(@NonNull byte[] sessionId) { closeSessionNative(sessionId); mPlaybackComponentMap.remove(ByteBuffer.wrap(sessionId)); } private native void closeSessionNative(@NonNull byte[] sessionId); private final Map mPlaybackComponentMap = new ConcurrentHashMap<>(); /** * This key request type species that the keys will be for online use, they will * not be saved to the device for subsequent use when the device is not connected * to a network. */ public static final int KEY_TYPE_STREAMING = 1; /** * This key request type specifies that the keys will be for offline use, they * will be saved to the device for use when the device is not connected to a network. */ public static final int KEY_TYPE_OFFLINE = 2; /** * This key request type specifies that previously saved offline keys should be released. */ public static final int KEY_TYPE_RELEASE = 3; /** @hide */ @IntDef({ KEY_TYPE_STREAMING, KEY_TYPE_OFFLINE, KEY_TYPE_RELEASE, }) @Retention(RetentionPolicy.SOURCE) public @interface KeyType {} /** * Contains the opaque data an app uses to request keys from a license server. * These request types may or may not be generated by a given plugin. Refer * to plugin vendor documentation for more information. */ public static final class KeyRequest { private byte[] mData; private String mDefaultUrl; private int mRequestType; /** * Key request type is initial license request. A license request * is necessary to load keys. */ public static final int REQUEST_TYPE_INITIAL = 0; /** * Key request type is license renewal. A license request is * necessary to prevent the keys from expiring. */ public static final int REQUEST_TYPE_RENEWAL = 1; /** * Key request type is license release */ public static final int REQUEST_TYPE_RELEASE = 2; /** * Keys are already loaded and are available for use. No license request is necessary, and * no key request data is returned. */ public static final int REQUEST_TYPE_NONE = 3; /** * Keys have been loaded but an additional license request is needed * to update their values. */ public static final int REQUEST_TYPE_UPDATE = 4; /** @hide */ @IntDef({ REQUEST_TYPE_INITIAL, REQUEST_TYPE_RENEWAL, REQUEST_TYPE_RELEASE, REQUEST_TYPE_NONE, REQUEST_TYPE_UPDATE, }) @Retention(RetentionPolicy.SOURCE) public @interface RequestType {} KeyRequest() {} /** * Get the opaque message data */ @NonNull public byte[] getData() { if (mData == null) { // this should never happen as mData is initialized in // JNI after construction of the KeyRequest object. The check // is needed here to guarantee @NonNull annotation. throw new RuntimeException("KeyRequest is not initialized"); } return mData; } /** * Get the default URL to use when sending the key request message to a * server, if known. The app may prefer to use a different license * server URL from other sources. * This method returns an empty string if the default URL is not known. */ @NonNull public String getDefaultUrl() { if (mDefaultUrl == null) { // this should never happen as mDefaultUrl is initialized in // JNI after construction of the KeyRequest object. The check // is needed here to guarantee @NonNull annotation. throw new RuntimeException("KeyRequest is not initialized"); } return mDefaultUrl; } /** * Get the type of the request */ @RequestType public int getRequestType() { return mRequestType; } }; /** * A key request/response exchange occurs between the app and a license server * to obtain or release keys used to decrypt encrypted content. *

* 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 optionalParameters) throws NotProvisionedException { HashMap internalParams; if (optionalParameters == null) { internalParams = new HashMap<>(); } else { internalParams = new HashMap<>(optionalParameters); } byte[] rawBytes = getNewestAvailablePackageCertificateRawBytes(); byte[] hashBytes = null; if (rawBytes != null) { hashBytes = getDigestBytes(rawBytes, "SHA-256"); } if (hashBytes != null) { Base64.Encoder encoderB64 = Base64.getEncoder(); String hashBytesB64 = encoderB64.encodeToString(hashBytes); internalParams.put("package_certificate_hash_bytes", hashBytesB64); } return getKeyRequestNative(scope, init, mimeType, keyType, internalParams); } @Nullable private byte[] getNewestAvailablePackageCertificateRawBytes() { Application application = ActivityThread.currentApplication(); if (application == null) { Log.w(TAG, "pkg cert: Application is null"); return null; } PackageManager pm = application.getPackageManager(); if (pm == null) { Log.w(TAG, "pkg cert: PackageManager is null"); return null; } PackageInfo packageInfo = null; try { packageInfo = pm.getPackageInfo(mAppPackageName, PackageManager.GET_SIGNING_CERTIFICATES); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, mAppPackageName, e); } if (packageInfo == null || packageInfo.signingInfo == null) { Log.w(TAG, "pkg cert: PackageInfo or SigningInfo is null"); return null; } Signature[] signers = packageInfo.signingInfo.getApkContentsSigners(); if (signers != null && signers.length == 1) { return signers[0].toByteArray(); } Log.w(TAG, "pkg cert: " + signers.length + " signers"); return null; } @Nullable private static byte[] getDigestBytes(@NonNull byte[] rawBytes, @NonNull String algorithm) { try { MessageDigest messageDigest = MessageDigest.getInstance(algorithm); return messageDigest.digest(rawBytes); } catch (NoSuchAlgorithmException e) { Log.w(TAG, algorithm, e); } return null; } @NonNull private native KeyRequest getKeyRequestNative( @NonNull byte[] scope, @Nullable byte[] init, @Nullable String mimeType, @KeyType int keyType, @Nullable HashMap optionalParameters) throws NotProvisionedException; /** * A key response is received from the license server by the app, then it is * provided to the MediaDrm instance using provideKeyResponse. When the * response is for an offline key request, a keySetId is returned that can be * used to later restore the keys to a new session with the method * {@link #restoreKeys}. * When the response is for a streaming or release request, an empty byte array * is returned. * * @param scope may be a sessionId or keySetId depending on the type of the * response. Scope should be set to the sessionId when the response is for either * streaming or offline key requests. Scope should be set to the keySetId when * the response is for a release request. * @param response the byte array response from the server * @return If the response is for an offline request, the keySetId for the offline * keys will be returned. If the response is for a streaming or release request * an empty byte array will be returned. * * @throws NotProvisionedException if the response indicates that * reprovisioning is required * @throws DeniedByServerException if the response indicates that the * server rejected the request */ @Nullable public native byte[] provideKeyResponse( @NonNull byte[] scope, @NonNull byte[] response) throws NotProvisionedException, DeniedByServerException; /** * Restore persisted offline keys into a new session. keySetId identifies the * keys to load, obtained from a prior call to {@link #provideKeyResponse}. * * @param sessionId the session ID for the DRM session * @param keySetId identifies the saved key set to restore */ public native void restoreKeys(@NonNull byte[] sessionId, @NonNull byte[] keySetId); /** * Remove the current keys from a session. * * @param sessionId the session ID for the DRM session */ public native void removeKeys(@NonNull byte[] sessionId); /** * Request an informative description of the key status for the session. The status is * in the form of {name, value} pairs. Since DRM license policies vary by vendor, * the specific status field names are determined by each DRM vendor. Refer to your * DRM provider documentation for definitions of the field names for a particular * DRM plugin. * * @param sessionId the session ID for the DRM session */ @NonNull public native HashMap queryKeyStatus(@NonNull byte[] sessionId); /** * Contains the opaque data an app uses to request a certificate from a provisioning * server */ public static final class ProvisionRequest { ProvisionRequest() {} /** * Get the opaque message data */ @NonNull public byte[] getData() { if (mData == null) { // this should never happen as mData is initialized in // JNI after construction of the KeyRequest object. The check // is needed here to guarantee @NonNull annotation. throw new RuntimeException("ProvisionRequest is not initialized"); } return mData; } /** * Get the default URL to use when sending the provision request * message to a server, if known. The app may prefer to use a different * provisioning server URL obtained from other sources. * This method returns an empty string if the default URL is not known. */ @NonNull public String getDefaultUrl() { if (mDefaultUrl == null) { // this should never happen as mDefaultUrl is initialized in // JNI after construction of the ProvisionRequest object. The check // is needed here to guarantee @NonNull annotation. throw new RuntimeException("ProvisionRequest is not initialized"); } return mDefaultUrl; } private byte[] mData; private String mDefaultUrl; } /** * A provision request/response exchange occurs between the app and a provisioning * server to retrieve a device certificate. If provisionining is required, the * EVENT_PROVISION_REQUIRED event will be sent to the event handler. * getProvisionRequest is used to obtain the opaque provision request byte array that * should be delivered to the provisioning server. The provision request byte array * is returned in ProvisionRequest.data. The recommended URL to deliver the provision * request to is returned in ProvisionRequest.defaultUrl. */ @NonNull public ProvisionRequest getProvisionRequest() { return getProvisionRequestNative(CERTIFICATE_TYPE_NONE, ""); } @NonNull private native ProvisionRequest getProvisionRequestNative(int certType, @NonNull String certAuthority); /** * After a provision response is received by the app, it is provided to the * MediaDrm instance using this method. * * @param response the opaque provisioning response byte array to provide to the * MediaDrm instance. * * @throws DeniedByServerException if the response indicates that the * server rejected the request */ public void provideProvisionResponse(@NonNull byte[] response) throws DeniedByServerException { provideProvisionResponseNative(response); } @NonNull 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. *

* 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 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. *

* 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 getSecureStops(); /** * Return a list of all secure stop IDs currently in persistent memory. * The secure stop ID can be used to access or remove the corresponding * secure stop. * * @return a list of secure stop IDs * @deprecated This method is deprecated and may be removed in a future * release. Use renewals by calling {@link #getKeyRequest} to track * concurrent playback. See additional information in * {@link #getSecureStops} */ @NonNull public native List getSecureStopIds(); /** * Access a specific secure stop given its secure stop ID. * Each secure stop has a unique ID. * * @param ssid the ID of the secure stop to return * @return the secure stop identified by ssid * @deprecated This method is deprecated and may be removed in a future * release. Use renewals by calling {@link #getKeyRequest} to track * concurrent playback. See additional information in * {@link #getSecureStops} */ @NonNull public native byte[] getSecureStop(@NonNull byte[] ssid); /** * Process the secure stop server response message ssRelease. After * authenticating the message, remove the secure stops identified in the * response. * * @param ssRelease the server response indicating which secure stops to release * @deprecated This method is deprecated and may be removed in a future * release. Use renewals by calling {@link #getKeyRequest} to track * concurrent playback. See additional information in * {@link #getSecureStops} */ public native void releaseSecureStops(@NonNull byte[] ssRelease); /** * Remove a specific secure stop without requiring a secure stop release message * from the license server. * @param ssid the ID of the secure stop to remove * @deprecated This method is deprecated and may be removed in a future * release. Use renewals by calling {@link #getKeyRequest} to track * concurrent playback. See additional information in * {@link #getSecureStops} */ public native void removeSecureStop(@NonNull byte[] ssid); /** * Remove all secure stops without requiring a secure stop release message from * the license server. * * This method was added in API 28. In API versions 18 through 27, * {@link #releaseAllSecureStops} should be called instead. There is no need to * do anything for API versions prior to 18. * @deprecated This method is deprecated and may be removed in a future * release. Use renewals by calling {@link #getKeyRequest} to track * concurrent playback. See additional information in * {@link #getSecureStops} */ public native void removeAllSecureStops(); /** * Remove all secure stops without requiring a secure stop release message from * the license server. * * @deprecated Remove all secure stops using {@link #removeAllSecureStops} instead. */ public void releaseAllSecureStops() { removeAllSecureStops();; } /** * @deprecated Not of any use for application development; * please note that the related integer constants remain supported: * {@link #HDCP_LEVEL_UNKNOWN}, * {@link #HDCP_NONE}, * {@link #HDCP_V1}, * {@link #HDCP_V2}, * {@link #HDCP_V2_1}, * {@link #HDCP_V2_2}, * {@link #HDCP_V2_3} * * @removed mistakenly exposed previously */ @Deprecated @Retention(RetentionPolicy.SOURCE) @IntDef({HDCP_LEVEL_UNKNOWN, HDCP_NONE, HDCP_V1, HDCP_V2, HDCP_V2_1, HDCP_V2_2, HDCP_V2_3, HDCP_NO_DIGITAL_OUTPUT}) public @interface HdcpLevel {} /** * The DRM plugin did not report an HDCP level, or an error * occurred accessing it */ public static final int HDCP_LEVEL_UNKNOWN = 0; /** * HDCP is not supported on this device, content is unprotected */ public static final int HDCP_NONE = 1; /** * HDCP version 1.0 */ public static final int HDCP_V1 = 2; /** * HDCP version 2.0 Type 1. */ public static final int HDCP_V2 = 3; /** * HDCP version 2.1 Type 1. */ public static final int HDCP_V2_1 = 4; /** * HDCP version 2.2 Type 1. */ public static final int HDCP_V2_2 = 5; /** * HDCP version 2.3 Type 1. */ public static final int HDCP_V2_3 = 6; /** * No digital output, implicitly secure */ public static final int HDCP_NO_DIGITAL_OUTPUT = Integer.MAX_VALUE; /** * Return the HDCP level negotiated with downstream receivers the * device is connected to. If multiple HDCP-capable displays are * simultaneously connected to separate interfaces, this method * returns the lowest negotiated level of all interfaces. *

* 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 getLogMessages(); /** * A {@link LogMessage} records an event in the {@link MediaDrm} framework * or vendor plugin. */ public static final class LogMessage { private final long timestampMillis; private final int priority; private final String message; /** * Timing of the recorded event measured in milliseconds since the Epoch, * 1970-01-01 00:00:00 +0000 (UTC). */ public final long getTimestampMillis() { return timestampMillis; } /** * Priority of the recorded event. *

* 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); } } }