diff --git a/RELEASENOTES.md b/RELEASENOTES.md index daacea1a5e5..83e6ee6d1f9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -28,6 +28,11 @@ Google TV, and Lenovo M10 FHD Plus that causes 60fps H265 streams to be marked as unsupported ([#966](https://github.com/androidx/media/issues/966)). +* 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)). * Effect: * Improved PQ to SDR tone-mapping by converting color spaces. * Session: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java index 9699fedc2f5..c071e377602 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSession.java @@ -396,8 +396,13 @@ private boolean openInternal() { return true; } catch (NotProvisionedException e) { provisioningManager.provisionRequired(this); - } catch (Exception e) { - onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM); + } catch (Exception | NoSuchMethodError e) { + // Work around b/291440132. + if (DrmUtil.isFailureToConstructNotProvisionedException(e)) { + provisioningManager.provisionRequired(this); + } else { + onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM); + } } return false; @@ -474,7 +479,7 @@ private boolean restoreKeys() { try { mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); return true; - } catch (Exception e) { + } catch (Exception | NoSuchMethodError e) { onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM); } return false; @@ -494,7 +499,7 @@ private void postKeyRequest(byte[] scope, int type, boolean allowRetry) { currentKeyRequest = mediaDrm.getKeyRequest(scope, schemeDatas, type, keyRequestParameters); Util.castNonNull(requestHandler) .post(MSG_KEYS, Assertions.checkNotNull(currentKeyRequest), allowRetry); - } catch (Exception e) { + } catch (Exception | NoSuchMethodError e) { onKeysError(e, /* thrownByExoMediaDrm= */ true); } } @@ -506,8 +511,8 @@ private void onKeyResponse(Object request, Object response) { } currentKeyRequest = null; - if (response instanceof Exception) { - onKeysError((Exception) response, /* thrownByExoMediaDrm= */ false); + if (response instanceof Exception || response instanceof NoSuchMethodError) { + onKeysError((Throwable) response, /* thrownByExoMediaDrm= */ false); return; } @@ -528,7 +533,7 @@ private void onKeyResponse(Object request, Object response) { state = STATE_OPENED_WITH_KEYS; dispatchEvent(DrmSessionEventListener.EventDispatcher::drmKeysLoaded); } - } catch (Exception e) { + } catch (Exception | NoSuchMethodError e) { onKeysError(e, /* thrownByExoMediaDrm= */ true); } } @@ -540,8 +545,12 @@ private void onKeysRequired() { } } - 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); } else { onError( @@ -552,11 +561,24 @@ private void onKeysError(Exception e, boolean thrownByExoMediaDrm) { } } - 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 = new DrmSessionException(e, DrmUtil.getErrorCodeForMediaDrmException(e, errorSource)); 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) { state = STATE_ERROR; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java index f3db12b3df3..1cdd390b1ad 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java @@ -657,11 +657,15 @@ private DefaultDrmSession createAndAcquireSessionWithRetry( } private static boolean acquisitionFailedIndicatingResourceShortage(DrmSession session) { + if (session.getState() != DrmSession.STATE_ERROR) { + return false; + } + + @Nullable Throwable cause = checkNotNull(session.getError()).getCause(); // ResourceBusyException is only available at API 19, so on earlier versions we // assume any error indicates resource shortage (ensuring we retry). - return session.getState() == DrmSession.STATE_ERROR - && (Util.SDK_INT < 19 - || checkNotNull(session.getError()).getCause() instanceof ResourceBusyException); + return Util.SDK_INT < 19 || cause instanceof ResourceBusyException + || DrmUtil.isFailureToConstructResourceBusyException(cause); } /** diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java index d75ca0793df..5ca04be073b 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java @@ -25,6 +25,7 @@ import android.media.MediaDrm; import android.media.MediaDrmResetException; import android.media.NotProvisionedException; +import android.media.ResourceBusyException; import androidx.annotation.DoNotInline; import androidx.annotation.IntDef; import androidx.annotation.Nullable; @@ -75,12 +76,13 @@ public final class DrmUtil { * exception. */ public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmException( - Exception exception, @ErrorSource int errorSource) { + Throwable exception, @ErrorSource int errorSource) { if (Util.SDK_INT >= 21 && Api21.isMediaDrmStateException(exception)) { return Api21.mediaDrmStateExceptionToErrorCode(exception); } else if (Util.SDK_INT >= 23 && Api23.isMediaDrmResetException(exception)) { return PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR; - } else if (Util.SDK_INT >= 18 && Api18.isNotProvisionedException(exception)) { + } else if ((Util.SDK_INT >= 18 && Api18.isNotProvisionedException(exception)) + || isFailureToConstructNotProvisionedException(exception)) { return PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED; } else if (Util.SDK_INT >= 18 && Api18.isDeniedByServerException(exception)) { 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;.("); + } + + /** + * 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;.("); + } + // Internal classes. @RequiresApi(18) diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerTest.java index 1103ee7178b..903a2039691 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerTest.java @@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLooper; /** Tests for {@link DefaultDrmSessionManager} and {@link DefaultDrmSession}. */ @@ -258,6 +259,21 @@ public void managerRelease_mediaDrmNotReleasedUntilLastSessionReleased() throws @Test(timeout = 10_000) 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 secondSchemeDatas = ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6))); FakeExoMediaDrm.LicenseServer licenseServer = @@ -267,7 +283,13 @@ public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() DrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder() .setUuidAndExoMediaDrmProvider( - DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm(/* maxConcurrentSessions= */ 1)) + DRM_SCHEME_UUID, + uuid -> + new FakeExoMediaDrm.Builder() + .setMaxConcurrentSessions(1) + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForResourceBusy) + .build()) .setSessionKeepaliveMs(10_000) .setMultiSession(true) .build(/* mediaDrmCallback= */ licenseServer); @@ -298,6 +320,23 @@ public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() @Test(timeout = 10_000) public void maxConcurrentSessionsExceeded_allPreacquiredAndKeepaliveSessionsEagerlyReleased() 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 secondSchemeDatas = ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6))); FakeExoMediaDrm.LicenseServer licenseServer = @@ -308,7 +347,12 @@ public void maxConcurrentSessionsExceeded_allPreacquiredAndKeepaliveSessionsEage new DefaultDrmSessionManager.Builder() .setUuidAndExoMediaDrmProvider( DRM_SCHEME_UUID, - uuid -> new FakeExoMediaDrm.Builder().setMaxConcurrentSessions(1).build()) + uuid -> + new FakeExoMediaDrm.Builder() + .setMaxConcurrentSessions(1) + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForResourceBusy) + .build()) .setSessionKeepaliveMs(10_000) .setMultiSession(true) .build(/* mediaDrmCallback= */ licenseServer); @@ -606,6 +650,22 @@ public void keyRefreshEvent_whileManagerIsReleasing_triggersKeyRefresh() throws @Test public void 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.allowingSchemeDatas(DRM_SCHEME_DATAS); @@ -613,7 +673,12 @@ public void keyRefreshEvent_whileManagerIsReleasing_triggersKeyRefresh() throws new DefaultDrmSessionManager.Builder() .setUuidAndExoMediaDrmProvider( DRM_SCHEME_UUID, - uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(1).build()) + uuid -> + new FakeExoMediaDrm.Builder() + .setProvisionsRequired(1) + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForNotProvisioned) + .build()) .build(/* mediaDrmCallback= */ licenseServer); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); drmSessionManager.prepare(); @@ -635,6 +700,22 @@ public void keyRefreshEvent_whileManagerIsReleasing_triggersKeyRefresh() throws @Test public void 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.allowingSchemeDatas(DRM_SCHEME_DATAS); @@ -646,6 +727,8 @@ public void keyRefreshEvent_whileManagerIsReleasing_triggersKeyRefresh() throws new FakeExoMediaDrm.Builder() .setProvisionsRequired(1) .throwNotProvisionedExceptionFromGetKeyRequest() + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForNotProvisioned) .build()) .build(/* mediaDrmCallback= */ licenseServer); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); @@ -665,6 +748,21 @@ public void keyRefreshEvent_whileManagerIsReleasing_triggersKeyRefresh() throws @Test 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.allowingSchemeDatas(DRM_SCHEME_DATAS); @@ -672,7 +770,12 @@ public void deviceNotProvisioned_doubleProvisioningHandledAndOpenSessionRetried( new DefaultDrmSessionManager.Builder() .setUuidAndExoMediaDrmProvider( DRM_SCHEME_UUID, - uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build()) + uuid -> + new FakeExoMediaDrm.Builder() + .setProvisionsRequired(2) + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForNotProvisioned) + .build()) .build(/* mediaDrmCallback= */ licenseServer); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); drmSessionManager.prepare(); @@ -693,6 +796,20 @@ public void deviceNotProvisioned_doubleProvisioningHandledAndOpenSessionRetried( @Test 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.requiringProvisioningThenAllowingSchemeDatas( DRM_SCHEME_DATAS); @@ -700,7 +817,12 @@ public void keyResponseIndicatesProvisioningRequired_provisioningDone() { DefaultDrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder() .setUuidAndExoMediaDrmProvider( - DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm.Builder().build()) + DRM_SCHEME_UUID, + uuid -> + new FakeExoMediaDrm.Builder() + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForNotProvisioned) + .build()) .build(/* mediaDrmCallback= */ licenseServer); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); drmSessionManager.prepare(); @@ -719,10 +841,29 @@ public void keyResponseIndicatesProvisioningRequired_provisioningDone() { @Test 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.allowingSchemeDatas(DRM_SCHEME_DATAS); - FakeExoMediaDrm mediaDrm = new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build(); + FakeExoMediaDrm mediaDrm = + new FakeExoMediaDrm.Builder() + .setProvisionsRequired(2) + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForNotProvisioned) + .build(); DefaultDrmSessionManager drmSessionManager = new DefaultDrmSessionManager.Builder() .setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, new AppManagedProvider(mediaDrm)) diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeExoMediaDrm.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeExoMediaDrm.java index 6e07539c960..484a7103b31 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeExoMediaDrm.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeExoMediaDrm.java @@ -16,6 +16,8 @@ package androidx.media3.test.utils; +import static androidx.media3.common.util.Assertions.checkState; + import android.media.DeniedByServerException; import android.media.MediaCryptoException; import android.media.MediaDrmException; @@ -72,6 +74,7 @@ public static class Builder { private int provisionsRequired; private boolean throwNotProvisionedExceptionFromGetKeyRequest; private int maxConcurrentSessions; + private boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy; /** Constructs an instance. */ public Builder() { @@ -119,6 +122,26 @@ public Builder throwNotProvisionedExceptionFromGetKeyRequest() { return this; } + /** + * Configures the {@link FakeExoMediaDrm} to throw {@link NoSuchMethodError} instead of {@link + * NotProvisionedException} or {@link ResourceBusyException}. + * + *

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. + * + *

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. * @@ -143,6 +166,7 @@ public FakeExoMediaDrm build() { enforceValidKeyResponses, provisionsRequired, throwNotProvisionedExceptionFromGetKeyRequest, + throwNoSuchMethodErrorForProvisioningAndResourceBusy, maxConcurrentSessions); } } @@ -170,6 +194,7 @@ public FakeExoMediaDrm build() { private final int provisionsRequired; private final int maxConcurrentSessions; private final boolean throwNotProvisionedExceptionFromGetKeyRequest; + private final boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy; private final Map byteProperties; private final Map stringProperties; private final Set> openSessionIds; @@ -184,12 +209,9 @@ public FakeExoMediaDrm build() { * @deprecated Use {@link Builder} instead. */ @Deprecated + @SuppressWarnings("deprecation") // Using deprecated constructor to reduce duplication. public FakeExoMediaDrm() { - this( - /* enforceValidKeyResponses= */ true, - /* provisionsRequired= */ 0, - /* throwNotProvisionedExceptionFromGetKeyRequest= */ false, - /* maxConcurrentSessions= */ Integer.MAX_VALUE); + this(/* maxConcurrentSessions= */ Integer.MAX_VALUE); } /** @@ -201,6 +223,7 @@ public FakeExoMediaDrm(int maxConcurrentSessions) { /* enforceValidKeyResponses= */ true, /* provisionsRequired= */ 0, /* throwNotProvisionedExceptionFromGetKeyRequest= */ false, + /* throwNoSuchMethodErrorForProvisioningAndResourceBusy= */ false, maxConcurrentSessions); } @@ -208,12 +231,15 @@ private FakeExoMediaDrm( boolean enforceValidKeyResponses, int provisionsRequired, boolean throwNotProvisionedExceptionFromGetKeyRequest, + boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy, int maxConcurrentSessions) { this.enforceValidKeyResponses = enforceValidKeyResponses; this.provisionsRequired = provisionsRequired; this.maxConcurrentSessions = maxConcurrentSessions; this.throwNotProvisionedExceptionFromGetKeyRequest = throwNotProvisionedExceptionFromGetKeyRequest; + this.throwNoSuchMethodErrorForProvisioningAndResourceBusy = + throwNoSuchMethodErrorForProvisioningAndResourceBusy; byteProperties = new HashMap<>(); stringProperties = new HashMap<>(); openSessionIds = new HashSet<>(); @@ -244,10 +270,16 @@ public void setOnExpirationUpdateListener(@Nullable OnExpirationUpdateListener l public byte[] openSession() throws MediaDrmException { Assertions.checkState(referenceCount > 0); if (!throwNotProvisionedExceptionFromGetKeyRequest && provisionsReceived < provisionsRequired) { - throw new NotProvisionedException("Not provisioned."); + throwNotProvisionedException(); } if (openSessionIds.size() >= maxConcurrentSessions) { - throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions); + if (throwNoSuchMethodErrorForProvisioningAndResourceBusy) { + throw new NoSuchMethodError( + "no non-static method" + + " \"Landroid/media/ResourceBusyException;.(Ljava/lang/String;III)V\""); + } else { + throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions); + } } byte[] sessionId = TestUtil.buildTestData(/* length= */ 10, sessionIdGenerator.incrementAndGet()); @@ -280,7 +312,7 @@ public KeyRequest getKeyRequest( } Assertions.checkArgument(keyType == KEY_TYPE_STREAMING, "Unrecognised keyType: " + keyType); if (throwNotProvisionedExceptionFromGetKeyRequest && provisionsReceived < provisionsRequired) { - throw new NotProvisionedException("Not provisioned."); + throwNotProvisionedException(); } Assertions.checkState(openSessionIds.contains(toByteList(scope))); Assertions.checkNotNull(schemeDatas); @@ -306,7 +338,7 @@ public byte[] provideKeyResponse(byte[] scope, byte[] response) throw new DeniedByServerException("Key request denied"); } if (responseAsList.equals(PROVISIONING_REQUIRED_RESPONSE)) { - throw new NotProvisionedException("Provisioning required"); + throwNotProvisionedException(); } if (enforceValidKeyResponses && !responseAsList.equals(VALID_KEY_RESPONSE)) { throw new IllegalArgumentException( @@ -451,6 +483,16 @@ public void resetProvisioning() { provisionsReceived = 0; } + private void throwNotProvisionedException() throws NotProvisionedException { + if (throwNoSuchMethodErrorForProvisioningAndResourceBusy) { + throw new NoSuchMethodError( + "no non-static method" + + " \"Landroid/media/NotProvisionedException;.(Ljava/lang/String;III)V\""); + } else { + throw new NotProvisionedException("Not provisioned."); + } + } + private static ImmutableList toByteList(byte[] byteArray) { return ImmutableList.copyOf(Bytes.asList(byteArray)); }