Add a test for the provisioning flow to DefaultDrmSessionManagerTest

#minor-release

PiperOrigin-RevId: 379913814
This commit is contained in:
ibaker 2021-06-17 10:39:53 +01:00 committed by Oliver Woodman
parent 6c05a469cb
commit 0f23fddeef
2 changed files with 104 additions and 16 deletions

View File

@ -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();
}

View File

@ -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.
*
* <p>An unprovisioned {@link FakeExoMediaDrm} will throw {@link NotProvisionedException} from
* {@link FakeExoMediaDrm#openSession()} until enough valid provisioning responses are passed to
* {@link FakeExoMediaDrm#provideProvisionResponse(byte[])}.
*
* <p>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.
*
* <p>If this is exceeded then subsequent calls to {@link FakeExoMediaDrm#openSession()} will
* throw {@link ResourceBusyException}.
*
* <p>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<Byte> 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<Byte> VALID_KEY_RESPONSE = TestUtil.createByteList(1, 2, 3);
private static final ImmutableList<Byte> KEY_DENIED_RESPONSE = TestUtil.createByteList(9, 8, 7);
private final int provisionsRequired;
private final int maxConcurrentSessions;
private final Map<String, byte[]> byteProperties;
private final Map<String, String> stringProperties;
@ -81,24 +134,24 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
private final Set<List<Byte>> 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