Add workarounds for NoSuchMethodError from DRM framework exceptions

Issue: androidx/media#1145

#minor-release

PiperOrigin-RevId: 613573868
This commit is contained in:
ibaker 2024-03-07 07:13:01 -08:00 committed by Copybara-Service
parent f02dc8e528
commit a604600126
6 changed files with 267 additions and 30 deletions

View File

@ -48,6 +48,10 @@
* Metadata: * Metadata:
* Image: * Image:
* DRM: * DRM:
* Work around a `NoSuchMethodError` which can be thrown by the `MediaDrm`
framework instead of `ResourceBusyException` or
`NotProvisionedException` on some Android 14 devices
([#1145](https://github.com/androidx/media/issues/1145)).
* DataSource: * DataSource:
* Implement support for `android.resource://package/id` raw resource URIs * Implement support for `android.resource://package/id` raw resource URIs
where `package` is different to the package of the current application. where `package` is different to the package of the current application.

View File

@ -394,9 +394,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return true; return true;
} catch (NotProvisionedException e) { } catch (NotProvisionedException e) {
provisioningManager.provisionRequired(this); provisioningManager.provisionRequired(this);
} catch (Exception e) { } catch (Exception | NoSuchMethodError e) {
// Work around b/291440132.
if (DrmUtil.isFailureToConstructNotProvisionedException(e)) {
provisioningManager.provisionRequired(this);
} else {
onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM); onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM);
} }
}
return false; return false;
} }
@ -472,7 +477,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
try { try {
mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId);
return true; return true;
} catch (Exception e) { } catch (Exception | NoSuchMethodError e) {
onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM); onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM);
} }
return false; return false;
@ -492,7 +497,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
currentKeyRequest = mediaDrm.getKeyRequest(scope, schemeDatas, type, keyRequestParameters); currentKeyRequest = mediaDrm.getKeyRequest(scope, schemeDatas, type, keyRequestParameters);
Util.castNonNull(requestHandler) Util.castNonNull(requestHandler)
.post(MSG_KEYS, Assertions.checkNotNull(currentKeyRequest), allowRetry); .post(MSG_KEYS, Assertions.checkNotNull(currentKeyRequest), allowRetry);
} catch (Exception e) { } catch (Exception | NoSuchMethodError e) {
onKeysError(e, /* thrownByExoMediaDrm= */ true); onKeysError(e, /* thrownByExoMediaDrm= */ true);
} }
} }
@ -504,8 +509,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
currentKeyRequest = null; currentKeyRequest = null;
if (response instanceof Exception) { if (response instanceof Exception || response instanceof NoSuchMethodError) {
onKeysError((Exception) response, /* thrownByExoMediaDrm= */ false); onKeysError((Throwable) response, /* thrownByExoMediaDrm= */ false);
return; return;
} }
@ -526,7 +531,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
state = STATE_OPENED_WITH_KEYS; state = STATE_OPENED_WITH_KEYS;
dispatchEvent(DrmSessionEventListener.EventDispatcher::drmKeysLoaded); dispatchEvent(DrmSessionEventListener.EventDispatcher::drmKeysLoaded);
} }
} catch (Exception e) { } catch (Exception | NoSuchMethodError e) {
onKeysError(e, /* thrownByExoMediaDrm= */ true); onKeysError(e, /* thrownByExoMediaDrm= */ true);
} }
} }
@ -538,8 +543,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
} }
private void onKeysError(Exception e, boolean thrownByExoMediaDrm) { /**
if (e instanceof NotProvisionedException) { * @param e Must be an instance of either {@link Exception} or {@link Error}.
*/
private void onKeysError(Throwable e, boolean thrownByExoMediaDrm) {
if (e instanceof NotProvisionedException
|| DrmUtil.isFailureToConstructNotProvisionedException(e)) {
provisioningManager.provisionRequired(this); provisioningManager.provisionRequired(this);
} else { } else {
onError( onError(
@ -550,11 +559,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
} }
private void onError(Exception e, @DrmUtil.ErrorSource int errorSource) { /**
* @param e Must be an instance of either {@link Exception} or {@link Error}.
*/
private void onError(Throwable e, @DrmUtil.ErrorSource int errorSource) {
lastException = lastException =
new DrmSessionException(e, DrmUtil.getErrorCodeForMediaDrmException(e, errorSource)); new DrmSessionException(e, DrmUtil.getErrorCodeForMediaDrmException(e, errorSource));
Log.e(TAG, "DRM session error", e); Log.e(TAG, "DRM session error", e);
dispatchEvent(eventDispatcher -> eventDispatcher.drmSessionManagerError(e)); if (e instanceof Exception) {
dispatchEvent(eventDispatcher -> eventDispatcher.drmSessionManagerError((Exception) e));
} else if (e instanceof Error) {
// Re-throw all Error types except a NoSuchMethodError caused by b/291440132.
if (!DrmUtil.isFailureToConstructResourceBusyException(e)
&& !DrmUtil.isFailureToConstructNotProvisionedException(e)) {
throw (Error) e;
}
} else {
throw new IllegalStateException("Unexpected Throwable subclass", e);
}
if (state != STATE_OPENED_WITH_KEYS) { if (state != STATE_OPENED_WITH_KEYS) {
state = STATE_ERROR; state = STATE_ERROR;
} }

View File

@ -655,8 +655,12 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
} }
private static boolean acquisitionFailedIndicatingResourceShortage(DrmSession session) { private static boolean acquisitionFailedIndicatingResourceShortage(DrmSession session) {
return session.getState() == DrmSession.STATE_ERROR if (session.getState() != DrmSession.STATE_ERROR) {
&& checkNotNull(session.getError()).getCause() instanceof ResourceBusyException; return false;
}
@Nullable Throwable cause = checkNotNull(session.getError()).getCause();
return cause instanceof ResourceBusyException
|| DrmUtil.isFailureToConstructResourceBusyException(cause);
} }
/** /**

View File

@ -25,6 +25,7 @@ import android.media.DeniedByServerException;
import android.media.MediaDrm; import android.media.MediaDrm;
import android.media.MediaDrmResetException; import android.media.MediaDrmResetException;
import android.media.NotProvisionedException; import android.media.NotProvisionedException;
import android.media.ResourceBusyException;
import androidx.annotation.DoNotInline; import androidx.annotation.DoNotInline;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -75,12 +76,13 @@ public final class DrmUtil {
* exception. * exception.
*/ */
public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmException( public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmException(
Exception exception, @ErrorSource int errorSource) { Throwable exception, @ErrorSource int errorSource) {
if (Util.SDK_INT >= 21 && Api21.isMediaDrmStateException(exception)) { if (Util.SDK_INT >= 21 && Api21.isMediaDrmStateException(exception)) {
return Api21.mediaDrmStateExceptionToErrorCode(exception); return Api21.mediaDrmStateExceptionToErrorCode(exception);
} else if (Util.SDK_INT >= 23 && Api23.isMediaDrmResetException(exception)) { } else if (Util.SDK_INT >= 23 && Api23.isMediaDrmResetException(exception)) {
return PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR; return PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR;
} else if (exception instanceof NotProvisionedException) { } else if (exception instanceof NotProvisionedException
|| isFailureToConstructNotProvisionedException(exception)) {
return PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED; return PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED;
} else if (exception instanceof DeniedByServerException) { } else if (exception instanceof DeniedByServerException) {
return PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED; return PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED;
@ -104,6 +106,28 @@ public final class DrmUtil {
} }
} }
/**
* Returns true if {@code e} represents a failure to construct a {@link NotProvisionedException}.
* See b/291440132.
*/
public static boolean isFailureToConstructNotProvisionedException(@Nullable Throwable e) {
return Util.SDK_INT == 34
&& e instanceof NoSuchMethodError
&& e.getMessage() != null
&& e.getMessage().contains("Landroid/media/NotProvisionedException;.<init>(");
}
/**
* Returns true if {@code e} represents a failure to construct a {@link ResourceBusyException}.
* See b/291440132.
*/
public static boolean isFailureToConstructResourceBusyException(@Nullable Throwable e) {
return Util.SDK_INT == 34
&& e instanceof NoSuchMethodError
&& e.getMessage() != null
&& e.getMessage().contains("Landroid/media/ResourceBusyException;.<init>(");
}
// Internal classes. // Internal classes.
@RequiresApi(21) @RequiresApi(21)

View File

@ -40,6 +40,7 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowLooper;
/** Tests for {@link DefaultDrmSessionManager} and {@link DefaultDrmSession}. */ /** Tests for {@link DefaultDrmSessionManager} and {@link DefaultDrmSession}. */
@ -258,6 +259,21 @@ public class DefaultDrmSessionManagerTest {
@Test(timeout = 10_000) @Test(timeout = 10_000)
public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception { public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception {
maxConcurrentSessionsExceededAllKeepAliveSessionsEagerlyReleased(
/* throwNoSuchMethodErrorForResourceBusy= */ false);
}
/** Testing workarounds for b/291440132. */
@Config(sdk = 34)
@Test(timeout = 10_000)
public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased_noSuchMethodError()
throws Exception {
maxConcurrentSessionsExceededAllKeepAliveSessionsEagerlyReleased(
/* throwNoSuchMethodErrorForResourceBusy= */ true);
}
private static void maxConcurrentSessionsExceededAllKeepAliveSessionsEagerlyReleased(
boolean throwNoSuchMethodErrorForResourceBusy) {
ImmutableList<DrmInitData.SchemeData> secondSchemeDatas = ImmutableList<DrmInitData.SchemeData> secondSchemeDatas =
ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6))); ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6)));
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
@ -267,7 +283,13 @@ public class DefaultDrmSessionManagerTest {
DrmSessionManager drmSessionManager = DrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder() new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider( .setUuidAndExoMediaDrmProvider(
DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm(/* maxConcurrentSessions= */ 1)) DRM_SCHEME_UUID,
uuid ->
new FakeExoMediaDrm.Builder()
.setMaxConcurrentSessions(1)
.throwNoSuchMethodErrorForProvisioningAndResourceBusy(
throwNoSuchMethodErrorForResourceBusy)
.build())
.setSessionKeepaliveMs(10_000) .setSessionKeepaliveMs(10_000)
.setMultiSession(true) .setMultiSession(true)
.build(/* mediaDrmCallback= */ licenseServer); .build(/* mediaDrmCallback= */ licenseServer);
@ -298,6 +320,23 @@ public class DefaultDrmSessionManagerTest {
@Test(timeout = 10_000) @Test(timeout = 10_000)
public void maxConcurrentSessionsExceeded_allPreacquiredAndKeepaliveSessionsEagerlyReleased() public void maxConcurrentSessionsExceeded_allPreacquiredAndKeepaliveSessionsEagerlyReleased()
throws Exception { throws Exception {
maxConcurrentSessionsExceededAllPreacquiredAndKeepaliveSessionsEagerlyReleased(
/* throwNoSuchMethodErrorForResourceBusy= */ false);
}
/** Testing workarounds for b/291440132. */
@Config(sdk = 34)
@Test(timeout = 10_000)
public void
maxConcurrentSessionsExceeded_allPreacquiredAndKeepaliveSessionsEagerlyReleased_noSuchMethodError()
throws Exception {
maxConcurrentSessionsExceededAllPreacquiredAndKeepaliveSessionsEagerlyReleased(
/* throwNoSuchMethodErrorForResourceBusy= */ true);
}
private static void
maxConcurrentSessionsExceededAllPreacquiredAndKeepaliveSessionsEagerlyReleased(
boolean throwNoSuchMethodErrorForResourceBusy) {
ImmutableList<DrmInitData.SchemeData> secondSchemeDatas = ImmutableList<DrmInitData.SchemeData> secondSchemeDatas =
ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6))); ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6)));
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
@ -308,7 +347,12 @@ public class DefaultDrmSessionManagerTest {
new DefaultDrmSessionManager.Builder() new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider( .setUuidAndExoMediaDrmProvider(
DRM_SCHEME_UUID, DRM_SCHEME_UUID,
uuid -> new FakeExoMediaDrm.Builder().setMaxConcurrentSessions(1).build()) uuid ->
new FakeExoMediaDrm.Builder()
.setMaxConcurrentSessions(1)
.throwNoSuchMethodErrorForProvisioningAndResourceBusy(
throwNoSuchMethodErrorForResourceBusy)
.build())
.setSessionKeepaliveMs(10_000) .setSessionKeepaliveMs(10_000)
.setMultiSession(true) .setMultiSession(true)
.build(/* mediaDrmCallback= */ licenseServer); .build(/* mediaDrmCallback= */ licenseServer);
@ -606,6 +650,22 @@ public class DefaultDrmSessionManagerTest {
@Test @Test
public void public void
deviceNotProvisioned_exceptionThrownFromOpenSession_provisioningDoneAndOpenSessionRetried() { deviceNotProvisioned_exceptionThrownFromOpenSession_provisioningDoneAndOpenSessionRetried() {
deviceNotProvisionedExceptionThrownFromOpenSessionProvisioningDoneAndOpenSessionRetried(
/* throwNoSuchMethodErrorForNotProvisioned= */ false);
}
/** Testing workarounds for b/291440132. */
@Config(sdk = 34)
@Test
public void
deviceNotProvisioned_exceptionThrownFromOpenSession_provisioningDoneAndOpenSessionRetried_noSuchMethodError() {
deviceNotProvisionedExceptionThrownFromOpenSessionProvisioningDoneAndOpenSessionRetried(
/* throwNoSuchMethodErrorForNotProvisioned= */ true);
}
private static void
deviceNotProvisionedExceptionThrownFromOpenSessionProvisioningDoneAndOpenSessionRetried(
boolean throwNoSuchMethodErrorForNotProvisioned) {
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
@ -613,7 +673,12 @@ public class DefaultDrmSessionManagerTest {
new DefaultDrmSessionManager.Builder() new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider( .setUuidAndExoMediaDrmProvider(
DRM_SCHEME_UUID, DRM_SCHEME_UUID,
uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(1).build()) uuid ->
new FakeExoMediaDrm.Builder()
.setProvisionsRequired(1)
.throwNoSuchMethodErrorForProvisioningAndResourceBusy(
throwNoSuchMethodErrorForNotProvisioned)
.build())
.build(/* mediaDrmCallback= */ licenseServer); .build(/* mediaDrmCallback= */ licenseServer);
drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET);
drmSessionManager.prepare(); drmSessionManager.prepare();
@ -635,6 +700,22 @@ public class DefaultDrmSessionManagerTest {
@Test @Test
public void public void
deviceNotProvisioned_exceptionThrownFromGetKeyRequest_provisioningDoneAndOpenSessionRetried() { deviceNotProvisioned_exceptionThrownFromGetKeyRequest_provisioningDoneAndOpenSessionRetried() {
deviceNotProvisionedExceptionThrownFromGetKeyRequestProvisioningDoneAndOpenSessionRetried(
/* throwNoSuchMethodErrorForNotProvisioned= */ false);
}
/** Testing workarounds for b/291440132. */
@Config(sdk = 34)
@Test
public void
deviceNotProvisioned_exceptionThrownFromGetKeyRequest_provisioningDoneAndOpenSessionRetried_noSuchMethodError() {
deviceNotProvisionedExceptionThrownFromGetKeyRequestProvisioningDoneAndOpenSessionRetried(
/* throwNoSuchMethodErrorForNotProvisioned= */ true);
}
private static void
deviceNotProvisionedExceptionThrownFromGetKeyRequestProvisioningDoneAndOpenSessionRetried(
boolean throwNoSuchMethodErrorForNotProvisioned) {
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
@ -646,6 +727,8 @@ public class DefaultDrmSessionManagerTest {
new FakeExoMediaDrm.Builder() new FakeExoMediaDrm.Builder()
.setProvisionsRequired(1) .setProvisionsRequired(1)
.throwNotProvisionedExceptionFromGetKeyRequest() .throwNotProvisionedExceptionFromGetKeyRequest()
.throwNoSuchMethodErrorForProvisioningAndResourceBusy(
throwNoSuchMethodErrorForNotProvisioned)
.build()) .build())
.build(/* mediaDrmCallback= */ licenseServer); .build(/* mediaDrmCallback= */ licenseServer);
drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET);
@ -665,6 +748,21 @@ public class DefaultDrmSessionManagerTest {
@Test @Test
public void deviceNotProvisioned_doubleProvisioningHandledAndOpenSessionRetried() { public void deviceNotProvisioned_doubleProvisioningHandledAndOpenSessionRetried() {
deviceNotProvisionedDoubleProvisioningHandledAndOpenSessionRetried(
/* throwNoSuchMethodErrorForNotProvisioned= */ false);
}
/** Testing workarounds for b/291440132. */
@Config(sdk = 34)
@Test
public void
deviceNotProvisioned_doubleProvisioningHandledAndOpenSessionRetried_noSuchMethodError() {
deviceNotProvisionedDoubleProvisioningHandledAndOpenSessionRetried(
/* throwNoSuchMethodErrorForNotProvisioned= */ true);
}
private static void deviceNotProvisionedDoubleProvisioningHandledAndOpenSessionRetried(
boolean throwNoSuchMethodErrorForNotProvisioned) {
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
@ -672,7 +770,12 @@ public class DefaultDrmSessionManagerTest {
new DefaultDrmSessionManager.Builder() new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider( .setUuidAndExoMediaDrmProvider(
DRM_SCHEME_UUID, DRM_SCHEME_UUID,
uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build()) uuid ->
new FakeExoMediaDrm.Builder()
.setProvisionsRequired(2)
.throwNoSuchMethodErrorForProvisioningAndResourceBusy(
throwNoSuchMethodErrorForNotProvisioned)
.build())
.build(/* mediaDrmCallback= */ licenseServer); .build(/* mediaDrmCallback= */ licenseServer);
drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET);
drmSessionManager.prepare(); drmSessionManager.prepare();
@ -693,6 +796,20 @@ public class DefaultDrmSessionManagerTest {
@Test @Test
public void keyResponseIndicatesProvisioningRequired_provisioningDone() { public void keyResponseIndicatesProvisioningRequired_provisioningDone() {
keyResponseIndicatesProvisioningRequiredProvisioningDone(
/* throwNoSuchMethodErrorForNotProvisioned= */ false);
}
/** Testing workarounds for b/291440132. */
@Config(sdk = 34)
@Test
public void keyResponseIndicatesProvisioningRequired_provisioningDone_noSuchMethodError() {
keyResponseIndicatesProvisioningRequiredProvisioningDone(
/* throwNoSuchMethodErrorForNotProvisioned= */ true);
}
private static void keyResponseIndicatesProvisioningRequiredProvisioningDone(
boolean throwNoSuchMethodErrorForNotProvisioned) {
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.requiringProvisioningThenAllowingSchemeDatas( FakeExoMediaDrm.LicenseServer.requiringProvisioningThenAllowingSchemeDatas(
DRM_SCHEME_DATAS); DRM_SCHEME_DATAS);
@ -700,7 +817,12 @@ public class DefaultDrmSessionManagerTest {
DefaultDrmSessionManager drmSessionManager = DefaultDrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder() new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider( .setUuidAndExoMediaDrmProvider(
DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm.Builder().build()) DRM_SCHEME_UUID,
uuid ->
new FakeExoMediaDrm.Builder()
.throwNoSuchMethodErrorForProvisioningAndResourceBusy(
throwNoSuchMethodErrorForNotProvisioned)
.build())
.build(/* mediaDrmCallback= */ licenseServer); .build(/* mediaDrmCallback= */ licenseServer);
drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET);
drmSessionManager.prepare(); drmSessionManager.prepare();
@ -719,10 +841,29 @@ public class DefaultDrmSessionManagerTest {
@Test @Test
public void provisioningUndoneWhileManagerIsActive_deviceReprovisioned() { public void provisioningUndoneWhileManagerIsActive_deviceReprovisioned() {
provisioningUndoneWhileManagerIsActiveDeviceReprovisioned(
/* throwNoSuchMethodErrorForNotProvisioned= */ false);
}
/** Testing workarounds for b/291440132. */
@Config(sdk = 34)
@Test
public void provisioningUndoneWhileManagerIsActive_deviceReprovisioned_noSuchMethodError() {
provisioningUndoneWhileManagerIsActiveDeviceReprovisioned(
/* throwNoSuchMethodErrorForNotProvisioned= */ true);
}
private static void provisioningUndoneWhileManagerIsActiveDeviceReprovisioned(
boolean throwNoSuchMethodErrorForNotProvisioned) {
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
FakeExoMediaDrm mediaDrm = new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build(); FakeExoMediaDrm mediaDrm =
new FakeExoMediaDrm.Builder()
.setProvisionsRequired(2)
.throwNoSuchMethodErrorForProvisioningAndResourceBusy(
throwNoSuchMethodErrorForNotProvisioned)
.build();
DefaultDrmSessionManager drmSessionManager = DefaultDrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder() new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, new AppManagedProvider(mediaDrm)) .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, new AppManagedProvider(mediaDrm))

View File

@ -16,6 +16,8 @@
package androidx.media3.test.utils; package androidx.media3.test.utils;
import static androidx.media3.common.util.Assertions.checkState;
import android.media.DeniedByServerException; import android.media.DeniedByServerException;
import android.media.MediaCryptoException; import android.media.MediaCryptoException;
import android.media.MediaDrmException; import android.media.MediaDrmException;
@ -72,6 +74,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
private int provisionsRequired; private int provisionsRequired;
private boolean throwNotProvisionedExceptionFromGetKeyRequest; private boolean throwNotProvisionedExceptionFromGetKeyRequest;
private int maxConcurrentSessions; private int maxConcurrentSessions;
private boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy;
/** Constructs an instance. */ /** Constructs an instance. */
public Builder() { public Builder() {
@ -119,6 +122,26 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
return this; return this;
} }
/**
* Configures the {@link FakeExoMediaDrm} to throw {@link NoSuchMethodError} instead of {@link
* NotProvisionedException} or {@link ResourceBusyException}.
*
* <p>This simulates a framework bug (b/291440132) introduced in API 34 and resolved by
* http://r.android.com/2770659, allowing us to test workarounds for the bug.
*
* <p>The default is {@code false}.
*/
@CanIgnoreReturnValue
public Builder throwNoSuchMethodErrorForProvisioningAndResourceBusy(
boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy) {
checkState(
!throwNoSuchMethodErrorForProvisioningAndResourceBusy || Util.SDK_INT == 34,
"The framework bug recreated by this method only exists on API 34.");
this.throwNoSuchMethodErrorForProvisioningAndResourceBusy =
throwNoSuchMethodErrorForProvisioningAndResourceBusy;
return this;
}
/** /**
* Sets the maximum number of concurrent sessions the {@link FakeExoMediaDrm} will support. * Sets the maximum number of concurrent sessions the {@link FakeExoMediaDrm} will support.
* *
@ -143,6 +166,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
enforceValidKeyResponses, enforceValidKeyResponses,
provisionsRequired, provisionsRequired,
throwNotProvisionedExceptionFromGetKeyRequest, throwNotProvisionedExceptionFromGetKeyRequest,
throwNoSuchMethodErrorForProvisioningAndResourceBusy,
maxConcurrentSessions); maxConcurrentSessions);
} }
} }
@ -170,6 +194,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
private final int provisionsRequired; private final int provisionsRequired;
private final int maxConcurrentSessions; private final int maxConcurrentSessions;
private final boolean throwNotProvisionedExceptionFromGetKeyRequest; private final boolean throwNotProvisionedExceptionFromGetKeyRequest;
private final boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy;
private final Map<String, byte[]> byteProperties; private final Map<String, byte[]> byteProperties;
private final Map<String, String> stringProperties; private final Map<String, String> stringProperties;
private final Set<List<Byte>> openSessionIds; private final Set<List<Byte>> openSessionIds;
@ -184,12 +209,9 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
* @deprecated Use {@link Builder} instead. * @deprecated Use {@link Builder} instead.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation") // Using deprecated constructor to reduce duplication.
public FakeExoMediaDrm() { public FakeExoMediaDrm() {
this( this(/* maxConcurrentSessions= */ Integer.MAX_VALUE);
/* enforceValidKeyResponses= */ true,
/* provisionsRequired= */ 0,
/* throwNotProvisionedExceptionFromGetKeyRequest= */ false,
/* maxConcurrentSessions= */ Integer.MAX_VALUE);
} }
/** /**
@ -201,6 +223,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
/* enforceValidKeyResponses= */ true, /* enforceValidKeyResponses= */ true,
/* provisionsRequired= */ 0, /* provisionsRequired= */ 0,
/* throwNotProvisionedExceptionFromGetKeyRequest= */ false, /* throwNotProvisionedExceptionFromGetKeyRequest= */ false,
/* throwNoSuchMethodErrorForProvisioningAndResourceBusy= */ false,
maxConcurrentSessions); maxConcurrentSessions);
} }
@ -208,12 +231,15 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
boolean enforceValidKeyResponses, boolean enforceValidKeyResponses,
int provisionsRequired, int provisionsRequired,
boolean throwNotProvisionedExceptionFromGetKeyRequest, boolean throwNotProvisionedExceptionFromGetKeyRequest,
boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy,
int maxConcurrentSessions) { int maxConcurrentSessions) {
this.enforceValidKeyResponses = enforceValidKeyResponses; this.enforceValidKeyResponses = enforceValidKeyResponses;
this.provisionsRequired = provisionsRequired; this.provisionsRequired = provisionsRequired;
this.maxConcurrentSessions = maxConcurrentSessions; this.maxConcurrentSessions = maxConcurrentSessions;
this.throwNotProvisionedExceptionFromGetKeyRequest = this.throwNotProvisionedExceptionFromGetKeyRequest =
throwNotProvisionedExceptionFromGetKeyRequest; throwNotProvisionedExceptionFromGetKeyRequest;
this.throwNoSuchMethodErrorForProvisioningAndResourceBusy =
throwNoSuchMethodErrorForProvisioningAndResourceBusy;
byteProperties = new HashMap<>(); byteProperties = new HashMap<>();
stringProperties = new HashMap<>(); stringProperties = new HashMap<>();
openSessionIds = new HashSet<>(); openSessionIds = new HashSet<>();
@ -244,11 +270,17 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
public byte[] openSession() throws MediaDrmException { public byte[] openSession() throws MediaDrmException {
Assertions.checkState(referenceCount > 0); Assertions.checkState(referenceCount > 0);
if (!throwNotProvisionedExceptionFromGetKeyRequest && provisionsReceived < provisionsRequired) { if (!throwNotProvisionedExceptionFromGetKeyRequest && provisionsReceived < provisionsRequired) {
throw new NotProvisionedException("Not provisioned."); throwNotProvisionedException();
} }
if (openSessionIds.size() >= maxConcurrentSessions) { if (openSessionIds.size() >= maxConcurrentSessions) {
if (throwNoSuchMethodErrorForProvisioningAndResourceBusy) {
throw new NoSuchMethodError(
"no non-static method"
+ " \"Landroid/media/ResourceBusyException;.<init>(Ljava/lang/String;III)V\"");
} else {
throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions); throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions);
} }
}
byte[] sessionId = byte[] sessionId =
TestUtil.buildTestData(/* length= */ 10, sessionIdGenerator.incrementAndGet()); TestUtil.buildTestData(/* length= */ 10, sessionIdGenerator.incrementAndGet());
if (!openSessionIds.add(toByteList(sessionId))) { if (!openSessionIds.add(toByteList(sessionId))) {
@ -280,7 +312,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
} }
Assertions.checkArgument(keyType == KEY_TYPE_STREAMING, "Unrecognised keyType: " + keyType); Assertions.checkArgument(keyType == KEY_TYPE_STREAMING, "Unrecognised keyType: " + keyType);
if (throwNotProvisionedExceptionFromGetKeyRequest && provisionsReceived < provisionsRequired) { if (throwNotProvisionedExceptionFromGetKeyRequest && provisionsReceived < provisionsRequired) {
throw new NotProvisionedException("Not provisioned."); throwNotProvisionedException();
} }
Assertions.checkState(openSessionIds.contains(toByteList(scope))); Assertions.checkState(openSessionIds.contains(toByteList(scope)));
Assertions.checkNotNull(schemeDatas); Assertions.checkNotNull(schemeDatas);
@ -306,7 +338,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
throw new DeniedByServerException("Key request denied"); throw new DeniedByServerException("Key request denied");
} }
if (responseAsList.equals(PROVISIONING_REQUIRED_RESPONSE)) { if (responseAsList.equals(PROVISIONING_REQUIRED_RESPONSE)) {
throw new NotProvisionedException("Provisioning required"); throwNotProvisionedException();
} }
if (enforceValidKeyResponses && !responseAsList.equals(VALID_KEY_RESPONSE)) { if (enforceValidKeyResponses && !responseAsList.equals(VALID_KEY_RESPONSE)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -451,6 +483,16 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
provisionsReceived = 0; provisionsReceived = 0;
} }
private void throwNotProvisionedException() throws NotProvisionedException {
if (throwNoSuchMethodErrorForProvisioningAndResourceBusy) {
throw new NoSuchMethodError(
"no non-static method"
+ " \"Landroid/media/NotProvisionedException;.<init>(Ljava/lang/String;III)V\"");
} else {
throw new NotProvisionedException("Not provisioned.");
}
}
private static ImmutableList<Byte> toByteList(byte[] byteArray) { private static ImmutableList<Byte> toByteList(byte[] byteArray) {
return ImmutableList.copyOf(Bytes.asList(byteArray)); return ImmutableList.copyOf(Bytes.asList(byteArray));
} }