Make PlaybackException be able to de-serialize subclasses

Otherwise, Player clients would not be able to benefit from
PlaybackException subclasses, like ExoPlaybackException.

PiperOrigin-RevId: 378873767
This commit is contained in:
aquilescanta 2021-06-11 16:05:58 +01:00 committed by Oliver Woodman
parent f5dee4d30c
commit c62e444c13
3 changed files with 56 additions and 21 deletions

View File

@ -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<PlaybackException> CREATOR = PlaybackException::new;
@SuppressWarnings({"unchecked"})
public static final Creator<PlaybackException> 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<PlaybackException> creator =
(Creator<PlaybackException>) 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());

View File

@ -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 =

View File

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