From a604600126e87037056a0d93b76b2d99e89544c4 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 7 Mar 2024 07:13:01 -0800 Subject: [PATCH] Add workarounds for `NoSuchMethodError` from DRM framework exceptions Issue: androidx/media#1145 #minor-release PiperOrigin-RevId: 613573868 --- RELEASENOTES.md | 4 + .../exoplayer/drm/DefaultDrmSession.java | 44 +++-- .../drm/DefaultDrmSessionManager.java | 8 +- .../media3/exoplayer/drm/DrmUtil.java | 28 +++- .../drm/DefaultDrmSessionManagerTest.java | 153 +++++++++++++++++- .../media3/test/utils/FakeExoMediaDrm.java | 60 +++++-- 6 files changed, 267 insertions(+), 30 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index affde98c5d..1d5bdebba4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -48,6 +48,10 @@ * Metadata: * Image: * 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: * Implement support for `android.resource://package/id` raw resource URIs where `package` is different to the package of the current application. 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 340ba92c75..d7f3b49470 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 @@ -394,8 +394,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; 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; @@ -472,7 +477,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; try { mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); return true; - } catch (Exception e) { + } catch (Exception | NoSuchMethodError e) { onError(e, DrmUtil.ERROR_SOURCE_EXO_MEDIA_DRM); } return false; @@ -492,7 +497,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; 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); } } @@ -504,8 +509,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } currentKeyRequest = null; - if (response instanceof Exception) { - onKeysError((Exception) response, /* thrownByExoMediaDrm= */ false); + if (response instanceof Exception || response instanceof NoSuchMethodError) { + onKeysError((Throwable) response, /* thrownByExoMediaDrm= */ false); return; } @@ -526,7 +531,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; state = STATE_OPENED_WITH_KEYS; dispatchEvent(DrmSessionEventListener.EventDispatcher::drmKeysLoaded); } - } catch (Exception e) { + } catch (Exception | NoSuchMethodError e) { 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); } else { 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 = 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 0376be83c9..63c69282a7 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 @@ -655,8 +655,12 @@ public class DefaultDrmSessionManager implements DrmSessionManager { } private static boolean acquisitionFailedIndicatingResourceShortage(DrmSession session) { - return session.getState() == DrmSession.STATE_ERROR - && checkNotNull(session.getError()).getCause() instanceof ResourceBusyException; + if (session.getState() != DrmSession.STATE_ERROR) { + return false; + } + @Nullable Throwable cause = checkNotNull(session.getError()).getCause(); + return 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 5da5817446..47af936ef8 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.DeniedByServerException; 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 (exception instanceof NotProvisionedException) { + } else if (exception instanceof NotProvisionedException + || isFailureToConstructNotProvisionedException(exception)) { return PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED; } else if (exception instanceof DeniedByServerException) { 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(21) 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 1103ee7178..903a203969 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.UUID; 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 class DefaultDrmSessionManagerTest { @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 class DefaultDrmSessionManagerTest { 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 class DefaultDrmSessionManagerTest { @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 class DefaultDrmSessionManagerTest { 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 class DefaultDrmSessionManagerTest { @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 class DefaultDrmSessionManagerTest { 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 class DefaultDrmSessionManagerTest { @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 class DefaultDrmSessionManagerTest { new FakeExoMediaDrm.Builder() .setProvisionsRequired(1) .throwNotProvisionedExceptionFromGetKeyRequest() + .throwNoSuchMethodErrorForProvisioningAndResourceBusy( + throwNoSuchMethodErrorForNotProvisioned) .build()) .build(/* mediaDrmCallback= */ licenseServer); drmSessionManager.setPlayer(/* playbackLooper= */ Looper.myLooper(), PlayerId.UNSET); @@ -665,6 +748,21 @@ public class DefaultDrmSessionManagerTest { @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 class DefaultDrmSessionManagerTest { 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 class DefaultDrmSessionManagerTest { @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 class DefaultDrmSessionManagerTest { 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 class DefaultDrmSessionManagerTest { @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 6e07539c96..484a7103b3 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 final class FakeExoMediaDrm implements ExoMediaDrm { private int provisionsRequired; private boolean throwNotProvisionedExceptionFromGetKeyRequest; private int maxConcurrentSessions; + private boolean throwNoSuchMethodErrorForProvisioningAndResourceBusy; /** Constructs an instance. */ public Builder() { @@ -119,6 +122,26 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { 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 final class FakeExoMediaDrm implements ExoMediaDrm { enforceValidKeyResponses, provisionsRequired, throwNotProvisionedExceptionFromGetKeyRequest, + throwNoSuchMethodErrorForProvisioningAndResourceBusy, maxConcurrentSessions); } } @@ -170,6 +194,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { 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 final class FakeExoMediaDrm implements ExoMediaDrm { * @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 final class FakeExoMediaDrm implements ExoMediaDrm { /* enforceValidKeyResponses= */ true, /* provisionsRequired= */ 0, /* throwNotProvisionedExceptionFromGetKeyRequest= */ false, + /* throwNoSuchMethodErrorForProvisioningAndResourceBusy= */ false, maxConcurrentSessions); } @@ -208,12 +231,15 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { 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 final class FakeExoMediaDrm implements ExoMediaDrm { 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 final class FakeExoMediaDrm implements ExoMediaDrm { } 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 final class FakeExoMediaDrm implements ExoMediaDrm { 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 final class FakeExoMediaDrm implements ExoMediaDrm { 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)); }