content: /*
   * 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.annotation.SystemApi;
  import android.annotation.UnsupportedAppUsage;
  import android.app.ActivityThread;
  import android.os.Handler;
  import android.os.HandlerExecutor;
  import android.os.Looper;
  import android.os.Message;
  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.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  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.
   * <p>
   * 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.
   * <p>
   * <p><img src="../../../images/mediadrm_overview.png"
   *      alt="MediaDrm Overview diagram"
   *      border="0" /></p>
   * <p>
   * 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.
   * <p>
   * A Provisioning server may be required to distribute device-unique credentials to
   * the devices.
   * <p>
   * 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.
   * <p>
   * The following sequence diagram shows the interactions between the objects
   * involved while playing back encrypted content:
   * <p>
   * <p><img src="../../../images/mediadrm_decryption_sequence.png"
   *         alt="MediaDrm Overview diagram"
   *         border="0" /></p>
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * <a name="Callbacks"></a>
   * <h3>Callbacks</h3>
   * <p>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;
  
      /**
       * 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.
       *
       * @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);
      }
  
      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 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++.
           */
          native_setup(new WeakReference<MediaDrm>(this),
                  getByteArrayFromUUID(uuid),  ActivityThread.currentOpPackageName());
  
          mCloseGuard.open("release");
      }
  
      /**
       * Thrown when an unrecoverable failure occurs during a MediaDrm operation.
       * Extends java.lang.IllegalStateException with the addition of an error
       * code that may be useful in diagnosing the failure.
       */
      public static final class MediaDrmStateException extends java.lang.IllegalStateException {
          private final int mErrorCode;
          private final String mDiagnosticInfo;
  
          /**
           * @hide
           */
          public MediaDrmStateException(int errorCode, @Nullable String detailMessage) {
              super(detailMessage);
              mErrorCode = errorCode;
  
              // TODO get this from DRM session
              final String sign = errorCode < 0 ? "neg_" : "";
              mDiagnosticInfo =
                  "android.media.MediaDrm.error_" + sign + Math.abs(errorCode);
  
          }
  
          /**
           * Retrieve the associated error code
           *
           * @hide
           */
          public int getErrorCode() {
              return mErrorCode;
          }
  
          /**
           * 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;
          }
      }
  
      /**
       * Thrown when an error occurs in any method that has a session context.
       */
      public static final class SessionException extends RuntimeException {
          public SessionException(int errorCode, @Nullable String detailMessage) {
              super(detailMessage);
              mErrorCode = errorCode;
          }
  
          /**
           * The SessionException has an unknown error code.
           */
          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.
           */
          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
           */
          @SessionErrorCode
          public int getErrorCode() {
              return mErrorCode;
          }
  
          private final int mErrorCode;
      }
  
      /**
       * 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<KeyStatus> 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<Integer, ListenerWithExecutor> mListenerMap = new ConcurrentHashMap<>();
  
      // called by old-style set*Listener APIs using Handlers; listener & handler are Nullable
      private <T> void setListenerWithHandler(int what, Handler handler, T listener,
              Function<T, Consumer<ListenerArgs>> 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 <T> void setListenerWithExecutor(int what, Executor executor, T listener,
              Function<T, Consumer<ListenerArgs>> 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 <T> void setGenericListener(int what, Executor executor, T listener,
              Function<T, Consumer<ListenerArgs>> converter) {
          mListenerMap.put(what, new ListenerWithExecutor(executor, converter.apply(listener)));
      }
  
      private void clearGenericListener(int what) {
          mListenerMap.remove(what);
      }
  
      private Consumer<ListenerArgs> createOnEventListener(OnEventListener listener) {
          return args -> {
              byte[] sessionId = args.parcel.createByteArray();
              if (sessionId.length == 0) {
                  sessionId = null;
              }
              byte[] data = args.parcel.createByteArray();
              if (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<ListenerArgs> createOnKeyStatusChangeListener(
              OnKeyStatusChangeListener listener) {
          return args -> {
              byte[] sessionId = args.parcel.createByteArray();
              if (sessionId.length > 0) {
                  List<KeyStatus> keyStatusList = keyStatusListFromParcel(args.parcel);
                  boolean hasNewUsableKey = (args.parcel.readInt() != 0);
  
                  Log.i(TAG, "Drm key status changed");
                  listener.onKeyStatusChange(this, sessionId, keyStatusList, hasNewUsableKey);
              }
          };
      }
  
      private Consumer<ListenerArgs> createOnExpirationUpdateListener(
              OnExpirationUpdateListener listener) {
          return args -> {
              byte[] sessionId = args.parcel.createByteArray();
              if (sessionId.length > 0) {
                  long expirationTime = args.parcel.readLong();
  
                  Log.i(TAG, "Drm key expiration update: " + expirationTime);
                  listener.onExpirationUpdate(this, sessionId, expirationTime);
              }
          };
      }
  
      private Consumer<ListenerArgs> createOnSessionLostStateListener(
              OnSessionLostStateListener listener) {
          return args -> {
              byte[] sessionId = args.parcel.createByteArray();
              Log.i(TAG, "Drm session lost state event: ");
              listener.onSessionLostState(this, sessionId);
          };
      }
  
      private static class ListenerArgs {
          private final Parcel parcel;
          private final int arg1;
          private final int arg2;
  
          public ListenerArgs(Parcel parcel, int arg1, int arg2) {
              this.parcel = parcel;
              this.arg1 = arg1;
              this.arg2 = arg2;
          }
      }
  
      private static class ListenerWithExecutor {
          private final Consumer<ListenerArgs> mConsumer;
          private final Executor mExecutor;
  
          public ListenerWithExecutor(Executor executor, Consumer<ListenerArgs> consumer) {
              this.mExecutor = executor;
              this.mConsumer = consumer;
          }
      }
  
      /**
       * Parse a list of KeyStatus objects from an event parcel
       */
      @NonNull
      private List<KeyStatus> keyStatusListFromParcel(@NonNull Parcel parcel) {
          int nelems = parcel.readInt();
          List<KeyStatus> 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, @Nullable Object obj)
      {
          MediaDrm md = (MediaDrm)((WeakReference<MediaDrm>)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;
                          }
                          if (obj != null && obj instanceof Parcel) {
                              Parcel p = (Parcel)obj;
                              listener.mConsumer.accept(new ListenerArgs(p, eventType, extra));
                          }
                      };
                      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.
       *
       * @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.
       *
       * @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 native byte[] openSession(@SecurityLevel int level) throws
              NotProvisionedException, ResourceBusyException;
  
      /**
       * Close a session on the MediaDrm object that was previously opened
       * with {@link #openSession}.
       */
      public native void closeSession(@NonNull byte[] sessionId);
  
      /**
       * 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.
       * <p>
       * 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.
       * <p>
       * 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 native KeyRequest getKeyRequest(
              @NonNull byte[] scope, @Nullable byte[] init,
              @Nullable String mimeType, @KeyType int keyType,
              @Nullable HashMap<String, String> 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<String, String> 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.
       * <p>
       * This method returns a list of the keySetIds for all offline licenses.
       * The offline license keySetId may be used to query the status of an
       * offline license with {@link #getOfflineLicenseState} or remove it with
       * {@link #removeOfflineLicense}.
       *
       * @return a list of offline license keySetIds
       */
      @NonNull
      public native List<byte[]> getOfflineLicenseKeySetIds();
  
      /**
       * Normally offline licenses are released using a key request/response
       * exchange using {@link #getKeyRequest} where the key type is
       * KEY_TYPE_RELEASE, followed by {@link #provideKeyResponse}. This allows
       * the server to cryptographically confirm that the license has been removed
       * and then adjust the count of offline licenses allocated to the device.
       * <p>
       * In some exceptional situations it may be necessary to directly remove
       * offline licenses without notifying the server, which may be performed
       * using this method.
       *
       * @param keySetId the id of the offline license to remove
       * @throws IllegalArgumentException if the keySetId does not refer to an
       * offline license.
       */
      public native void removeOfflineLicense(@NonNull byte[] keySetId);
  
      /**
       * Offline license state is unknown, an error occurred while trying
       * to access it.
       */
      public static final int OFFLINE_LICENSE_STATE_UNKNOWN = 0;
  
      /**
       * Offline license 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.
       * <p>
       * 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.
       * <p>
       * 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}.
       * <p>
       * 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
       */
      @NonNull
      public native List<byte[]> 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
       */
      @NonNull
      public native List<byte[]> 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
       */
      @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
       */
      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
       */
      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.
       */
      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}
       */
      @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.
       * <p>
       * 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.
       * <p>
       * @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.
       * <p>
       * @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}
       */
      @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.
       */
      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.
       * <p>
       * @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.
       * <p>
       * 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.
       * <p>
       * 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.
       * <p>
       * 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.
       * <p>
       * 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.
       * <p>
       * 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.
       * <p>
       * 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".
       * <p>
       * 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.
       * <p>
       * 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);
      }
  
      @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();
          }
      }
  
      /** @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 extraact 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";
      }
  }
  