diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index 0b1b4ba380..25546acb95 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -28,14 +28,19 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * A {@link DrmSession} that supports playbacks using {@link ExoMediaDrm}. @@ -84,7 +89,7 @@ import java.util.UUID; private final ExoMediaDrm mediaDrm; private final ProvisioningManager provisioningManager; private final @DefaultDrmSessionManager.Mode int mode; - private final HashMap optionalKeyRequestParameters; + private final @Nullable HashMap optionalKeyRequestParameters; private final EventDispatcher eventDispatcher; private final int initialDrmRequestRetryCount; @@ -96,13 +101,13 @@ import java.util.UUID; private int openCount; private HandlerThread requestHandlerThread; private PostRequestHandler postRequestHandler; - private T mediaCrypto; - private DrmSessionException lastException; - private byte[] sessionId; - private @Nullable byte[] offlineLicenseKeySetId; + private @Nullable T mediaCrypto; + private @Nullable DrmSessionException lastException; + private byte @MonotonicNonNull [] sessionId; + private byte @MonotonicNonNull [] offlineLicenseKeySetId; - private KeyRequest currentKeyRequest; - private ProvisionRequest currentProvisionRequest; + private @Nullable KeyRequest currentKeyRequest; + private @Nullable ProvisionRequest currentProvisionRequest; /** * Instantiates a new DRM session. @@ -129,18 +134,25 @@ import java.util.UUID; @Nullable List schemeDatas, @DefaultDrmSessionManager.Mode int mode, @Nullable byte[] offlineLicenseKeySetId, - HashMap optionalKeyRequestParameters, + @Nullable HashMap optionalKeyRequestParameters, MediaDrmCallback callback, Looper playbackLooper, EventDispatcher eventDispatcher, int initialDrmRequestRetryCount) { + if (mode == DefaultDrmSessionManager.MODE_QUERY + || mode == DefaultDrmSessionManager.MODE_RELEASE) { + Assertions.checkNotNull(offlineLicenseKeySetId); + } this.uuid = uuid; this.provisioningManager = provisioningManager; this.mediaDrm = mediaDrm; this.mode = mode; - this.offlineLicenseKeySetId = offlineLicenseKeySetId; - this.schemeDatas = - offlineLicenseKeySetId == null ? Collections.unmodifiableList(schemeDatas) : null; + if (offlineLicenseKeySetId != null) { + this.offlineLicenseKeySetId = offlineLicenseKeySetId; + this.schemeDatas = null; + } else { + this.schemeDatas = Collections.unmodifiableList(Assertions.checkNotNull(schemeDatas)); + } this.optionalKeyRequestParameters = optionalKeyRequestParameters; this.callback = callback; this.initialDrmRequestRetryCount = initialDrmRequestRetryCount; @@ -166,9 +178,9 @@ import java.util.UUID; } } - /** - * @return True if the session is closed and cleaned up, false otherwise. - */ + /** @return True if the session is closed and cleaned up, false otherwise. */ + // Assigning null to various non-null variables for clean-up. Class won't be used after release. + @SuppressWarnings("assignment.type.incompatible") public boolean release() { if (--openCount == 0) { state = STATE_RELEASED; @@ -245,33 +257,35 @@ import java.util.UUID; } @Override - public final DrmSessionException getError() { + public final @Nullable DrmSessionException getError() { return state == STATE_ERROR ? lastException : null; } @Override - public final T getMediaCrypto() { + public final @Nullable T getMediaCrypto() { return mediaCrypto; } @Override - public Map queryKeyStatus() { + public @Nullable Map queryKeyStatus() { return sessionId == null ? null : mediaDrm.queryKeyStatus(sessionId); } @Override - public byte[] getOfflineLicenseKeySetId() { + public @Nullable byte[] getOfflineLicenseKeySetId() { return offlineLicenseKeySetId; } // Internal methods. /** - * Try to open a session, do provisioning if necessary. - * @param allowProvisioning if provisioning is allowed, set this to false when calling from - * processing provision response. - * @return true on success, false otherwise. + * Try to open a session, do provisioning if necessary. + * + * @param allowProvisioning if provisioning is allowed, set this to false when calling from + * processing provision response. + * @return true on success, false otherwise. */ + @EnsuresNonNullIf(result = true, expression = "sessionId") private boolean openInternal(boolean allowProvisioning) { if (isOpen()) { // Already opened @@ -319,19 +333,20 @@ import java.util.UUID; provisioningManager.onProvisionCompleted(); } + @RequiresNonNull("sessionId") private void doLicense(boolean allowRetry) { switch (mode) { case DefaultDrmSessionManager.MODE_PLAYBACK: case DefaultDrmSessionManager.MODE_QUERY: if (offlineLicenseKeySetId == null) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_STREAMING, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_STREAMING, allowRetry); } else if (state == STATE_OPENED_WITH_KEYS || restoreKeys()) { long licenseDurationRemainingSec = getLicenseDurationRemainingSec(); if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && licenseDurationRemainingSec <= MAX_LICENSE_DURATION_TO_RENEW) { Log.d(TAG, "Offline license has expired or will expire soon. " + "Remaining seconds: " + licenseDurationRemainingSec); - postKeyRequest(ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); } else if (licenseDurationRemainingSec <= 0) { onError(new KeysExpiredException()); } else { @@ -342,19 +357,20 @@ import java.util.UUID; break; case DefaultDrmSessionManager.MODE_DOWNLOAD: if (offlineLicenseKeySetId == null) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); } else { // Renew if (restoreKeys()) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); } } break; case DefaultDrmSessionManager.MODE_RELEASE: + Assertions.checkNotNull(offlineLicenseKeySetId); // It's not necessary to restore the key (and open a session to do that) before releasing it // but this serves as a good sanity/fast-failure check. if (restoreKeys()) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_RELEASE, allowRetry); + postKeyRequest(offlineLicenseKeySetId, ExoMediaDrm.KEY_TYPE_RELEASE, allowRetry); } break; default: @@ -362,6 +378,7 @@ import java.util.UUID; } } + @RequiresNonNull({"sessionId", "offlineLicenseKeySetId"}) private boolean restoreKeys() { try { mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); @@ -377,12 +394,12 @@ import java.util.UUID; if (!C.WIDEVINE_UUID.equals(uuid)) { return Long.MAX_VALUE; } - Pair pair = WidevineUtil.getLicenseDurationRemainingSec(this); + Pair pair = + Assertions.checkNotNull(WidevineUtil.getLicenseDurationRemainingSec(this)); return Math.min(pair.first, pair.second); } - private void postKeyRequest(int type, boolean allowRetry) { - byte[] scope = type == ExoMediaDrm.KEY_TYPE_RELEASE ? offlineLicenseKeySetId : sessionId; + private void postKeyRequest(byte[] scope, int type, boolean allowRetry) { try { currentKeyRequest = mediaDrm.getKeyRequest(scope, schemeDatas, type, optionalKeyRequestParameters); @@ -407,7 +424,7 @@ import java.util.UUID; try { byte[] responseData = (byte[]) response; if (mode == DefaultDrmSessionManager.MODE_RELEASE) { - mediaDrm.provideKeyResponse(offlineLicenseKeySetId, responseData); + mediaDrm.provideKeyResponse(Util.castNonNull(offlineLicenseKeySetId), responseData); eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmKeysRestored); } else { byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, responseData); @@ -447,6 +464,8 @@ import java.util.UUID; } } + @EnsuresNonNullIf(result = true, expression = "sessionId") + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") private boolean isOpen() { return state == STATE_OPENED || state == STATE_OPENED_WITH_KEYS; } @@ -461,8 +480,9 @@ import java.util.UUID; } @Override + @SuppressWarnings("unchecked") public void handleMessage(Message msg) { - Pair requestAndResponse = (Pair) msg.obj; + Pair requestAndResponse = (Pair) msg.obj; Object request = requestAndResponse.first; Object response = requestAndResponse.second; switch (msg.what) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 0e751ca49e..03269f0caa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -21,7 +21,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.IntDef; -import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DefaultDrmSession.ProvisioningManager; @@ -94,7 +94,7 @@ public class DefaultDrmSessionManager implements DrmSe private final UUID uuid; private final ExoMediaDrm mediaDrm; private final MediaDrmCallback callback; - private final HashMap optionalKeyRequestParameters; + private final @Nullable HashMap optionalKeyRequestParameters; private final EventDispatcher eventDispatcher; private final boolean multiSession; private final int initialDrmRequestRetryCount; @@ -102,11 +102,11 @@ public class DefaultDrmSessionManager implements DrmSe private final List> sessions; private final List> provisioningSessions; - private Looper playbackLooper; + private @Nullable Looper playbackLooper; private int mode; - private byte[] offlineLicenseKeySetId; + private @Nullable byte[] offlineLicenseKeySetId; - /* package */ volatile MediaDrmHandler mediaDrmHandler; + /* package */ volatile @Nullable MediaDrmHandler mediaDrmHandler; /** * @deprecated Use {@link #newWidevineInstance(MediaDrmCallback, HashMap)} and {@link @@ -115,9 +115,9 @@ public class DefaultDrmSessionManager implements DrmSe @Deprecated public static DefaultDrmSessionManager newWidevineInstance( MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) throws UnsupportedDrmException { DefaultDrmSessionManager drmSessionManager = newWidevineInstance(callback, optionalKeyRequestParameters); @@ -136,7 +136,7 @@ public class DefaultDrmSessionManager implements DrmSe * @throws UnsupportedDrmException If the specified DRM scheme is not supported. */ public static DefaultDrmSessionManager newWidevineInstance( - MediaDrmCallback callback, HashMap optionalKeyRequestParameters) + MediaDrmCallback callback, @Nullable HashMap optionalKeyRequestParameters) throws UnsupportedDrmException { return newFrameworkInstance(C.WIDEVINE_UUID, callback, optionalKeyRequestParameters); } @@ -148,9 +148,9 @@ public class DefaultDrmSessionManager implements DrmSe @Deprecated public static DefaultDrmSessionManager newPlayReadyInstance( MediaDrmCallback callback, - String customData, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) + @Nullable String customData, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) throws UnsupportedDrmException { DefaultDrmSessionManager drmSessionManager = newPlayReadyInstance(callback, customData); @@ -171,7 +171,7 @@ public class DefaultDrmSessionManager implements DrmSe * @throws UnsupportedDrmException If the specified DRM scheme is not supported. */ public static DefaultDrmSessionManager newPlayReadyInstance( - MediaDrmCallback callback, String customData) throws UnsupportedDrmException { + MediaDrmCallback callback, @Nullable String customData) throws UnsupportedDrmException { HashMap optionalKeyRequestParameters; if (!TextUtils.isEmpty(customData)) { optionalKeyRequestParameters = new HashMap<>(); @@ -190,9 +190,9 @@ public class DefaultDrmSessionManager implements DrmSe public static DefaultDrmSessionManager newFrameworkInstance( UUID uuid, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) throws UnsupportedDrmException { DefaultDrmSessionManager drmSessionManager = newFrameworkInstance(uuid, callback, optionalKeyRequestParameters); @@ -212,7 +212,9 @@ public class DefaultDrmSessionManager implements DrmSe * @throws UnsupportedDrmException If the specified DRM scheme is not supported. */ public static DefaultDrmSessionManager newFrameworkInstance( - UUID uuid, MediaDrmCallback callback, HashMap optionalKeyRequestParameters) + UUID uuid, + MediaDrmCallback callback, + @Nullable HashMap optionalKeyRequestParameters) throws UnsupportedDrmException { return new DefaultDrmSessionManager<>( uuid, @@ -228,13 +230,14 @@ public class DefaultDrmSessionManager implements DrmSe * and {@link #addListener(Handler, DefaultDrmSessionEventListener)}. */ @Deprecated + @SuppressWarnings("method.invocation.invalid") public DefaultDrmSessionManager( UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) { + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) { this(uuid, mediaDrm, callback, optionalKeyRequestParameters); if (eventHandler != null && eventListener != null) { addListener(eventHandler, eventListener); @@ -252,7 +255,7 @@ public class DefaultDrmSessionManager implements DrmSe UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters) { + @Nullable HashMap optionalKeyRequestParameters) { this( uuid, mediaDrm, @@ -267,13 +270,14 @@ public class DefaultDrmSessionManager implements DrmSe * boolean)} and {@link #addListener(Handler, DefaultDrmSessionEventListener)}. */ @Deprecated + @SuppressWarnings("method.invocation.invalid") public DefaultDrmSessionManager( UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener, + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener, boolean multiSession) { this(uuid, mediaDrm, callback, optionalKeyRequestParameters, multiSession); if (eventHandler != null && eventListener != null) { @@ -294,7 +298,7 @@ public class DefaultDrmSessionManager implements DrmSe UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, + @Nullable HashMap optionalKeyRequestParameters, boolean multiSession) { this( uuid, @@ -310,13 +314,14 @@ public class DefaultDrmSessionManager implements DrmSe * boolean, int)} and {@link #addListener(Handler, DefaultDrmSessionEventListener)}. */ @Deprecated + @SuppressWarnings("method.invocation.invalid") public DefaultDrmSessionManager( UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener, + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener, boolean multiSession, int initialDrmRequestRetryCount) { this( @@ -346,7 +351,7 @@ public class DefaultDrmSessionManager implements DrmSe UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, + @Nullable HashMap optionalKeyRequestParameters, boolean multiSession, int initialDrmRequestRetryCount) { Assertions.checkNotNull(uuid); @@ -443,21 +448,22 @@ public class DefaultDrmSessionManager implements DrmSe * required. * *

{@code mode} must be one of these: + * *

    - *
  • {@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is - * requested otherwise the offline license is restored. - *
  • {@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license - * is restored. - *
  • {@link #MODE_DOWNLOAD}: If {@code offlineLicenseKeySetId} is null, an offline license is - * requested otherwise the offline license is renewed. - *
  • {@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline license - * is released. + *
  • {@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is + * requested otherwise the offline license is restored. + *
  • {@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license + * is restored. + *
  • {@link #MODE_DOWNLOAD}: If {@code offlineLicenseKeySetId} is null, an offline license is + * requested otherwise the offline license is renewed. + *
  • {@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline + * license is released. *
* * @param mode The mode to be set. * @param offlineLicenseKeySetId The key set id of the license to be used with the given mode. */ - public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) { + public void setMode(@Mode int mode, @Nullable byte[] offlineLicenseKeySetId) { Assertions.checkState(sessions.isEmpty()); if (mode == MODE_QUERY || mode == MODE_RELEASE) { Assertions.checkNotNull(offlineLicenseKeySetId); @@ -469,7 +475,7 @@ public class DefaultDrmSessionManager implements DrmSe // DrmSessionManager implementation. @Override - public boolean canAcquireSession(@NonNull DrmInitData drmInitData) { + public boolean canAcquireSession(DrmInitData drmInitData) { if (offlineLicenseKeySetId != null) { // An offline license can be restored so a session can always be acquired. return true; @@ -650,10 +656,14 @@ public class DefaultDrmSessionManager implements DrmSe private class MediaDrmEventListener implements OnEventListener { @Override - public void onEvent(ExoMediaDrm md, byte[] sessionId, int event, int extra, - byte[] data) { + public void onEvent( + ExoMediaDrm md, + byte[] sessionId, + int event, + int extra, + @Nullable byte[] data) { if (mode == DefaultDrmSessionManager.MODE_PLAYBACK) { - mediaDrmHandler.obtainMessage(event, sessionId).sendToTarget(); + Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java index b9415c74af..ef96c7ae75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java @@ -85,10 +85,8 @@ public final class DrmInitData implements Comparator, Parcelable { // Lazily initialized hashcode. private int hashCode; - /** - * The protection scheme type, or null if not applicable or unknown. - */ - @Nullable public final String schemeType; + /** The protection scheme type, or null if not applicable or unknown. */ + public final @Nullable String schemeType; /** * Number of {@link SchemeData}s. @@ -106,7 +104,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param schemeType See {@link #schemeType}. * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ - public DrmInitData(String schemeType, List schemeDatas) { + public DrmInitData(@Nullable String schemeType, List schemeDatas) { this(schemeType, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); } @@ -131,11 +129,11 @@ public final class DrmInitData implements Comparator, Parcelable { if (cloneSchemeDatas) { schemeDatas = schemeDatas.clone(); } - // Sorting ensures that universal scheme data (i.e. data that applies to all schemes) is matched - // last. It's also required by the equals and hashcode implementations. - Arrays.sort(schemeDatas, this); this.schemeDatas = schemeDatas; schemeDataCount = schemeDatas.length; + // Sorting ensures that universal scheme data (i.e. data that applies to all schemes) is matched + // last. It's also required by the equals and hashcode implementations. + Arrays.sort(this.schemeDatas, this); } /* package */ DrmInitData(Parcel in) { @@ -152,7 +150,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @return The initialization data for the scheme, or null if the scheme is not supported. */ @Deprecated - public SchemeData get(UUID uuid) { + public @Nullable SchemeData get(UUID uuid) { for (SchemeData schemeData : schemeDatas) { if (schemeData.matches(uuid)) { return schemeData; @@ -270,10 +268,8 @@ public final class DrmInitData implements Comparator, Parcelable { public final @Nullable String licenseServerUrl; /** The mimeType of {@link #data}. */ public final String mimeType; - /** - * The initialization data. May be null for scheme support checks only. - */ - public final byte[] data; + /** The initialization data. May be null for scheme support checks only. */ + public final @Nullable byte[] data; /** * Whether secure decryption is required. */ @@ -285,7 +281,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param mimeType See {@link #mimeType}. * @param data See {@link #data}. */ - public SchemeData(UUID uuid, String mimeType, byte[] data) { + public SchemeData(UUID uuid, String mimeType, @Nullable byte[] data) { this(uuid, mimeType, data, false); } @@ -296,7 +292,8 @@ public final class DrmInitData implements Comparator, Parcelable { * @param data See {@link #data}. * @param requiresSecureDecryption See {@link #requiresSecureDecryption}. */ - public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) { + public SchemeData( + UUID uuid, String mimeType, @Nullable byte[] data, boolean requiresSecureDecryption) { this(uuid, /* licenseServerUrl= */ null, mimeType, data, requiresSecureDecryption); } @@ -312,7 +309,7 @@ public final class DrmInitData implements Comparator, Parcelable { UUID uuid, @Nullable String licenseServerUrl, String mimeType, - byte[] data, + @Nullable byte[] data, boolean requiresSecureDecryption) { this.uuid = Assertions.checkNotNull(uuid); this.licenseServerUrl = licenseServerUrl; @@ -324,7 +321,7 @@ public final class DrmInitData implements Comparator, Parcelable { /* package */ SchemeData(Parcel in) { uuid = new UUID(in.readLong(), in.readLong()); licenseServerUrl = in.readString(); - mimeType = in.readString(); + mimeType = Util.castNonNull(in.readString()); data = in.createByteArray(); requiresSecureDecryption = in.readByte() != 0; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index bed3545d78..698f6fdb25 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.drm; import android.annotation.TargetApi; import android.media.MediaDrm; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; @@ -75,21 +76,24 @@ public interface DrmSession { @State int getState(); /** - * Returns the cause of the error state. + * Returns the cause of the error state, or null if {@link #getState()} is not {@link + * #STATE_ERROR}. */ + @Nullable DrmSessionException getError(); /** * Returns a {@link ExoMediaCrypto} for the open session, or null if called before the session has * been opened or after it's been released. */ + @Nullable T getMediaCrypto(); /** * Returns a map describing the key status for the session, or null if called before the session * has been opened or after it's been released. - *

- * Since DRM license policies vary by vendor, the specific status field names are determined by + * + *

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 engine plugin. * @@ -97,12 +101,13 @@ public interface DrmSession { * has been opened or after it's been released. * @see MediaDrm#queryKeyStatus(byte[]) */ + @Nullable Map queryKeyStatus(); /** * Returns the key set id of the offline license loaded into this session, or null if there isn't * one. */ + @Nullable byte[] getOfflineLicenseKeySetId(); - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java index d30e670c3f..f0335b50c9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.drm; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.util.Assertions; import java.util.Map; @@ -33,22 +34,22 @@ public final class ErrorStateDrmSession implements Drm } @Override - public DrmSessionException getError() { + public @Nullable DrmSessionException getError() { return error; } @Override - public T getMediaCrypto() { + public @Nullable T getMediaCrypto() { return null; } @Override - public Map queryKeyStatus() { + public @Nullable Map queryKeyStatus() { return null; } @Override - public byte[] getOfflineLicenseKeySetId() { + public @Nullable byte[] getOfflineLicenseKeySetId() { return null; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 254b524b30..1f862546ae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -131,7 +131,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm= 23 && version == 1) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java index 8b8c3bec99..4ff0af3c0e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.drm; import android.annotation.TargetApi; import android.net.Uri; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; @@ -135,8 +136,12 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { return executePost(dataSourceFactory, url, request.getData(), requestProperties); } - private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url, - byte[] data, Map requestProperties) throws IOException { + private static byte[] executePost( + HttpDataSource.Factory dataSourceFactory, + String url, + byte[] data, + @Nullable Map requestProperties) + throws IOException { HttpDataSource dataSource = dataSourceFactory.createDataSource(); if (requestProperties != null) { for (Map.Entry requestProperty : requestProperties.entrySet()) { @@ -164,17 +169,18 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { boolean manuallyRedirect = (e.responseCode == 307 || e.responseCode == 308) && manualRedirectCount++ < MAX_MANUAL_REDIRECTS; - url = manuallyRedirect ? getRedirectUrl(e) : null; - if (url == null) { + String redirectUrl = manuallyRedirect ? getRedirectUrl(e) : null; + if (redirectUrl == null) { throw e; } + url = redirectUrl; } finally { Util.closeQuietly(inputStream); } } } - private static String getRedirectUrl(InvalidResponseCodeException exception) { + private static @Nullable String getRedirectUrl(InvalidResponseCodeException exception) { Map> headerFields = exception.headerFields; if (headerFields != null) { List locationHeaders = headerFields.get("Location"); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java index 9298c16cb0..d596b4ab90 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java @@ -19,6 +19,7 @@ import android.media.MediaDrm; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; +import android.support.annotation.Nullable; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.Mode; @@ -34,6 +35,8 @@ import java.util.UUID; */ public final class OfflineLicenseHelper { + private static final DrmInitData DUMMY_DRM_INIT_DATA = new DrmInitData(); + private final ConditionVariable conditionVariable; private final DefaultDrmSessionManager drmSessionManager; private final HandlerThread handlerThread; @@ -95,7 +98,7 @@ public final class OfflineLicenseHelper { String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory, - HashMap optionalKeyRequestParameters) + @Nullable HashMap optionalKeyRequestParameters) throws UnsupportedDrmException { return new OfflineLicenseHelper<>(C.WIDEVINE_UUID, FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), @@ -118,7 +121,7 @@ public final class OfflineLicenseHelper { UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters) { + @Nullable HashMap optionalKeyRequestParameters) { handlerThread = new HandlerThread("OfflineLicenseHelper"); handlerThread.start(); conditionVariable = new ConditionVariable(); @@ -199,7 +202,8 @@ public final class OfflineLicenseHelper { public synchronized byte[] renewLicense(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null); + return blockingKeyRequest( + DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, DUMMY_DRM_INIT_DATA); } /** @@ -211,7 +215,8 @@ public final class OfflineLicenseHelper { public synchronized void releaseLicense(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - blockingKeyRequest(DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, null); + blockingKeyRequest( + DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, DUMMY_DRM_INIT_DATA); } /** @@ -224,8 +229,9 @@ public final class OfflineLicenseHelper { public synchronized Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - DrmSession drmSession = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY, - offlineLicenseKeySetId, null); + DrmSession drmSession = + openBlockingKeyRequest( + DefaultDrmSessionManager.MODE_QUERY, offlineLicenseKeySetId, DUMMY_DRM_INIT_DATA); DrmSessionException error = drmSession.getError(); Pair licenseDurationRemainingSec = WidevineUtil.getLicenseDurationRemainingSec(drmSession); @@ -236,7 +242,7 @@ public final class OfflineLicenseHelper { } throw error; } - return licenseDurationRemainingSec; + return Assertions.checkNotNull(licenseDurationRemainingSec); } /** @@ -246,8 +252,9 @@ public final class OfflineLicenseHelper { handlerThread.quit(); } - private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, - DrmInitData drmInitData) throws DrmSessionException { + private byte[] blockingKeyRequest( + @Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, DrmInitData drmInitData) + throws DrmSessionException { DrmSession drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, drmInitData); DrmSessionException error = drmSession.getError(); @@ -256,11 +263,11 @@ public final class OfflineLicenseHelper { if (error != null) { throw error; } - return keySetId; + return Assertions.checkNotNull(keySetId); } - private DrmSession openBlockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, - DrmInitData drmInitData) { + private DrmSession openBlockingKeyRequest( + @Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, DrmInitData drmInitData) { drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId); conditionVariable.close(); DrmSession drmSession = drmSessionManager.acquireSession(handlerThread.getLooper(), diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java index 45c38b3609..b8b80490a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.drm; +import android.support.annotation.Nullable; import android.util.Pair; import com.google.android.exoplayer2.C; import java.util.Map; @@ -38,7 +39,8 @@ public final class WidevineUtil { * @return A {@link Pair} consisting of the remaining license and playback durations in seconds, * or null if called before the session has been opened or after it's been released. */ - public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { + public static @Nullable Pair getLicenseDurationRemainingSec( + DrmSession drmSession) { Map keyStatus = drmSession.queryKeyStatus(); if (keyStatus == null) { return null; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java index 6f62b7fcfc..bba487f321 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java @@ -92,9 +92,12 @@ public class OfflineLicenseHelperTest { public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception { setStubLicenseAndPlaybackDurationValues(1000, 200); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); - - assertThat(offlineLicenseKeySetId).isNull(); + try { + offlineLicenseHelper.downloadLicense(newDrmInitData()); + fail(); + } catch (Exception e) { + // Expected. + } } @Test