From 0f23fddeef288a8355ffc2f115b714e9963d2ef9 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 17 Jun 2021 10:39:53 +0100 Subject: [PATCH] Add a test for the provisioning flow to DefaultDrmSessionManagerTest #minor-release PiperOrigin-RevId: 379913814 --- .../drm/DefaultDrmSessionManagerTest.java | 35 ++++++-- .../exoplayer2/testutil/FakeExoMediaDrm.java | 85 ++++++++++++++++--- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java index d58a55e444..cc3c5f0bd7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java @@ -43,9 +43,7 @@ import org.robolectric.shadows.ShadowLooper; // TODO: Test more branches: // - Different sources for licenseServerUrl. // - Multiple acquisitions & releases for same keys -> multiple requests. -// - Provisioning. // - Key denial. -// - Handling of ResourceBusyException (indicating session scarcity). @RunWith(AndroidJUnit4.class) public class DefaultDrmSessionManagerTest { @@ -538,6 +536,33 @@ public class DefaultDrmSessionManagerTest { exoMediaDrm.release(); } + @Test + public void deviceNotProvisioned_provisioningDoneAndOpenSessionRetried() { + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + + DefaultDrmSessionManager drmSessionManager = + new DefaultDrmSessionManager.Builder() + .setUuidAndExoMediaDrmProvider( + DRM_SCHEME_UUID, + uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(1).build()) + .build(/* mediaDrmCallback= */ licenseServer); + drmSessionManager.prepare(); + DrmSession drmSession = + checkNotNull( + drmSessionManager.acquireSession( + /* playbackLooper= */ checkNotNull(Looper.myLooper()), + /* eventDispatcher= */ null, + FORMAT_WITH_DRM_INIT_DATA)); + // Confirm the device isn't provisioned (otherwise state would be OPENED) + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING); + waitForOpenedWithKeys(drmSession); + + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); + assertThat(drmSession.queryKeyStatus()) + .containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE); + } + @Test public void managerNotPrepared_acquireSessionAndPreacquireSessionFail() throws Exception { FakeExoMediaDrm.LicenseServer licenseServer = @@ -602,10 +627,10 @@ public class DefaultDrmSessionManagerTest { } private static void waitForOpenedWithKeys(DrmSession drmSession) { - // Check the error first, so we get a meaningful failure if there's been an error. - assertThat(drmSession.getError()).isNull(); - assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED); while (drmSession.getState() != DrmSession.STATE_OPENED_WITH_KEYS) { + // Check the error first, so we get a meaningful failure if there's been an error. + assertThat(drmSession.getError()).isNull(); + assertThat(drmSession.getState()).isAnyOf(DrmSession.STATE_OPENING, DrmSession.STATE_OPENED); // Allow the key response to be handled. ShadowLooper.idleMainLooper(); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java index 14663985ab..608347c569 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java @@ -39,6 +39,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Bytes; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -61,8 +62,59 @@ import java.util.concurrent.atomic.AtomicInteger; @RequiresApi(29) public final class FakeExoMediaDrm implements ExoMediaDrm { + /** Builder for {@link FakeExoMediaDrm} instances. */ + public static class Builder { + private int provisionsRequired; + private int maxConcurrentSessions; + + /** Constructs an instance. */ + public Builder() { + provisionsRequired = 0; + maxConcurrentSessions = Integer.MAX_VALUE; + } + + /** + * Sets how many successful provisioning round trips are needed for the {@link FakeExoMediaDrm} + * to be provisioned. + * + *

An unprovisioned {@link FakeExoMediaDrm} will throw {@link NotProvisionedException} from + * {@link FakeExoMediaDrm#openSession()} until enough valid provisioning responses are passed to + * {@link FakeExoMediaDrm#provideProvisionResponse(byte[])}. + * + *

Defaults to 0 (i.e. device is already provisioned). + */ + public Builder setProvisionsRequired(int provisionsRequired) { + this.provisionsRequired = provisionsRequired; + return this; + } + + /** + * Sets the maximum number of concurrent sessions the {@link FakeExoMediaDrm} will support. + * + *

If this is exceeded then subsequent calls to {@link FakeExoMediaDrm#openSession()} will + * throw {@link ResourceBusyException}. + * + *

Defaults to {@link Integer#MAX_VALUE}. + */ + public Builder setMaxConcurrentSessions(int maxConcurrentSessions) { + this.maxConcurrentSessions = maxConcurrentSessions; + return this; + } + + /** + * Returns a {@link FakeExoMediaDrm} instance with an initial reference count of 1. The caller + * is responsible for calling {@link FakeExoMediaDrm#release()} when they no longer need the + * instance. + */ + public FakeExoMediaDrm build() { + return new FakeExoMediaDrm(provisionsRequired, maxConcurrentSessions); + } + } + public static final ProvisionRequest FAKE_PROVISION_REQUEST = new ProvisionRequest(TestUtil.createByteArray(7, 8, 9), "bar.test"); + public static final ImmutableList VALID_PROVISION_RESPONSE = + TestUtil.createByteList(4, 5, 6); /** Key for use with the Map returned from {@link FakeExoMediaDrm#queryKeyStatus(byte[])}. */ public static final String KEY_STATUS_KEY = "KEY_STATUS"; @@ -74,6 +126,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { private static final ImmutableList VALID_KEY_RESPONSE = TestUtil.createByteList(1, 2, 3); private static final ImmutableList KEY_DENIED_RESPONSE = TestUtil.createByteList(9, 8, 7); + private final int provisionsRequired; private final int maxConcurrentSessions; private final Map byteProperties; private final Map stringProperties; @@ -81,24 +134,24 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { private final Set> sessionIdsWithValidKeys; private final AtomicInteger sessionIdGenerator; + private int provisionsReceived; private int referenceCount; @Nullable private OnEventListener onEventListener; - /** - * Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls - * to {@link #openSession()} with no limit on the number of concurrent open sessions. - */ + /** @deprecated Use {@link Builder} instead. */ + @Deprecated public FakeExoMediaDrm() { this(/* maxConcurrentSessions= */ Integer.MAX_VALUE); } - /** - * Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls - * to {@link #openSession()} with a limit on the number of concurrent open sessions. - * - * @param maxConcurrentSessions The max number of sessions allowed to be open simultaneously. - */ + /** @deprecated Use {@link Builder} instead. */ + @Deprecated public FakeExoMediaDrm(int maxConcurrentSessions) { + this(/* provisionsRequired= */ 0, maxConcurrentSessions); + } + + private FakeExoMediaDrm(int provisionsRequired, int maxConcurrentSessions) { + this.provisionsRequired = provisionsRequired; this.maxConcurrentSessions = maxConcurrentSessions; byteProperties = new HashMap<>(); stringProperties = new HashMap<>(); @@ -129,6 +182,9 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { @Override public byte[] openSession() throws MediaDrmException { Assertions.checkState(referenceCount > 0); + if (provisionsReceived < provisionsRequired) { + throw new NotProvisionedException("Not provisioned."); + } if (openSessionIds.size() >= maxConcurrentSessions) { throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions); } @@ -200,6 +256,9 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { @Override public void provideProvisionResponse(byte[] response) throws DeniedByServerException { Assertions.checkState(referenceCount > 0); + if (Bytes.asList(response).equals(VALID_PROVISION_RESPONSE)) { + provisionsReceived++; + } } @Override @@ -340,7 +399,11 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { @Override public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws MediaDrmCallbackException { - return new byte[0]; + if (Arrays.equals(request.getData(), FAKE_PROVISION_REQUEST.getData())) { + return Bytes.toArray(VALID_PROVISION_RESPONSE); + } else { + return Util.EMPTY_BYTE_ARRAY; + } } @Override