diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index 58f18bdcb1..90ee2f81ea 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; /** Thrown when a non locally recoverable playback failure occurs. */ public class PlaybackException extends Exception implements Bundleable { @@ -340,6 +341,7 @@ public class PlaybackException extends Exception implements Bundleable { @IntDef( open = true, value = { + FIELD_STRING_CLASS_NAME, FIELD_INT_ERROR_CODE, FIELD_LONG_TIMESTAMP_MS, FIELD_STRING_MESSAGE, @@ -348,11 +350,12 @@ public class PlaybackException extends Exception implements Bundleable { }) protected @interface FieldNumber {} - private static final int FIELD_INT_ERROR_CODE = 0; - private static final int FIELD_LONG_TIMESTAMP_MS = 1; - private static final int FIELD_STRING_MESSAGE = 2; - private static final int FIELD_STRING_CAUSE_CLASS_NAME = 3; - private static final int FIELD_STRING_CAUSE_MESSAGE = 4; + private static final int FIELD_STRING_CLASS_NAME = 0; + private static final int FIELD_INT_ERROR_CODE = 1; + private static final int FIELD_LONG_TIMESTAMP_MS = 2; + private static final int FIELD_STRING_MESSAGE = 3; + private static final int FIELD_STRING_CAUSE_CLASS_NAME = 4; + private static final int FIELD_STRING_CAUSE_MESSAGE = 5; /** * Defines a minimum field id value for subclasses to use when implementing {@link #toBundle()} @@ -364,12 +367,33 @@ public class PlaybackException extends Exception implements Bundleable { protected static final int FIELD_CUSTOM_ID_BASE = 1000; /** Object that can create a {@link PlaybackException} from a {@link Bundle}. */ - public static final Creator CREATOR = PlaybackException::new; + @SuppressWarnings({"unchecked"}) + public static final Creator CREATOR = + bundle -> { + String className = bundle.getString(keyForField(FIELD_STRING_CLASS_NAME)); + if (className != null && !PlaybackException.class.getName().equals(className)) { + try { + Field creatorField = Class.forName(className).getField("CREATOR"); + // It is ok to pass null to Field.get for static fields. + @SuppressWarnings("argument.type.incompatible") + Creator creator = + (Creator) creatorField.get(/* obj= */ null); + if (creator != null) { + return creator.fromBundle(bundle); + } + } catch (Throwable e) { + // Failed to create an instance using the creator from the class with the given name. + // Fall through and try to deserialize the PlaybackException fields only. + } + } + return new PlaybackException(bundle); + }; @CallSuper @Override public Bundle toBundle() { Bundle bundle = new Bundle(); + bundle.putString(keyForField(FIELD_STRING_CLASS_NAME), getClass().getName()); bundle.putInt(keyForField(FIELD_INT_ERROR_CODE), errorCode); bundle.putLong(keyForField(FIELD_LONG_TIMESTAMP_MS), timestampMs); bundle.putString(keyForField(FIELD_STRING_MESSAGE), getMessage()); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/ExoPlaybackExceptionTest.java b/library/common/src/test/java/com/google/android/exoplayer2/ExoPlaybackExceptionTest.java index 86d9aefb13..5c8372b466 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/ExoPlaybackExceptionTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/ExoPlaybackExceptionTest.java @@ -34,6 +34,14 @@ public class ExoPlaybackExceptionTest { assertThat(areExoPlaybackExceptionsEqual(before, after)).isTrue(); } + @Test + public void roundTripViaBundle_usingPlaybackExceptionCreator_yieldsEqualInstance() { + ExoPlaybackException before = ExoPlaybackException.createForRemote(/* message= */ "test"); + ExoPlaybackException after = + (ExoPlaybackException) PlaybackException.CREATOR.fromBundle(before.toBundle()); + assertThat(areExoPlaybackExceptionsEqual(before, after)).isTrue(); + } + @Test public void roundTripViaBundle_ofExoPlaybackExceptionTypeRenderer_yieldsEqualInstance() { ExoPlaybackException before = diff --git a/library/common/src/test/java/com/google/android/exoplayer2/PlaybackExceptionTest.java b/library/common/src/test/java/com/google/android/exoplayer2/PlaybackExceptionTest.java index 363bdb9d15..527b5ecc2b 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/PlaybackExceptionTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/PlaybackExceptionTest.java @@ -54,11 +54,13 @@ public class PlaybackExceptionTest { /* timestampMs= */ 1000); Bundle bundle = new Bundle(); - bundle.putInt("0", 5001); // Error code - bundle.putLong("1", 1000); // Timestamp. - bundle.putString("2", "message"); - bundle.putString("3", expectedCause.getClass().getName()); - bundle.putString("4", "cause message"); + // We purposefully omit the class name to test that PlaybackException fields are deserialized, + // even though it was not possible to create an instance using the class name in the bundle. + bundle.putInt("1", 5001); // Error code + bundle.putLong("2", 1000); // Timestamp. + bundle.putString("3", "message"); + bundle.putString("4", expectedCause.getClass().getName()); + bundle.putString("5", "cause message"); assertPlaybackExceptionsAreEquivalent( expectedException, PlaybackException.CREATOR.fromBundle(bundle)); @@ -75,11 +77,12 @@ public class PlaybackExceptionTest { /* timestampMs= */ 2000); Bundle bundle = exception.toBundle(); - assertThat(bundle.getInt("0")).isEqualTo(4002); // Error code. - assertThat(bundle.getLong("1")).isEqualTo(2000); // Timestamp. - assertThat(bundle.getString("2")).isEqualTo("message"); - assertThat(bundle.getString("3")).isEqualTo(cause.getClass().getName()); - assertThat(bundle.getString("4")).isEqualTo("cause message"); + assertThat(bundle.getString("0")).isEqualTo(PlaybackException.class.getName()); + assertThat(bundle.getInt("1")).isEqualTo(4002); // Error code. + assertThat(bundle.getLong("2")).isEqualTo(2000); // Timestamp. + assertThat(bundle.getString("3")).isEqualTo("message"); + assertThat(bundle.getString("4")).isEqualTo(cause.getClass().getName()); + assertThat(bundle.getString("5")).isEqualTo("cause message"); } @Test @@ -93,11 +96,11 @@ public class PlaybackExceptionTest { /* timestampMs= */ 1000); Bundle bundle = new Bundle(); - bundle.putInt("0", 5001); // Error code - bundle.putLong("1", 1000); // Timestamp. - bundle.putString("2", "message"); - bundle.putString("3", "invalid cause class name"); - bundle.putString("4", "cause message"); + bundle.putInt("1", 5001); // Error code + bundle.putLong("2", 1000); // Timestamp. + bundle.putString("3", "message"); + bundle.putString("4", "invalid cause class name"); + bundle.putString("5", "cause message"); assertPlaybackExceptionsAreEquivalent( expectedException, PlaybackException.CREATOR.fromBundle(bundle));