499 lines
20 KiB
Java
499 lines
20 KiB
Java
/*
|
|
* Copyright 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.google.android.exoplayer2;
|
|
|
|
import static java.lang.annotation.ElementType.FIELD;
|
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
|
import static java.lang.annotation.ElementType.METHOD;
|
|
import static java.lang.annotation.ElementType.PARAMETER;
|
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
|
|
import android.net.ConnectivityManager;
|
|
import android.os.Bundle;
|
|
import android.os.RemoteException;
|
|
import android.os.SystemClock;
|
|
import android.text.TextUtils;
|
|
import androidx.annotation.CallSuper;
|
|
import androidx.annotation.IntDef;
|
|
import androidx.annotation.Nullable;
|
|
import com.google.android.exoplayer2.util.Clock;
|
|
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.annotation.Target;
|
|
|
|
/** Thrown when a non locally recoverable playback failure occurs. */
|
|
public class PlaybackException extends Exception implements Bundleable {
|
|
|
|
/**
|
|
* Codes that identify causes of player errors.
|
|
*
|
|
* <p>This list of errors may be extended in future versions, and {@link Player} implementations
|
|
* may define custom error codes.
|
|
*/
|
|
@Documented
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
|
@IntDef(
|
|
open = true,
|
|
value = {
|
|
ERROR_CODE_UNSPECIFIED,
|
|
ERROR_CODE_REMOTE_ERROR,
|
|
ERROR_CODE_BEHIND_LIVE_WINDOW,
|
|
ERROR_CODE_TIMEOUT,
|
|
ERROR_CODE_FAILED_RUNTIME_CHECK,
|
|
ERROR_CODE_IO_UNSPECIFIED,
|
|
ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
|
|
ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,
|
|
ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE,
|
|
ERROR_CODE_IO_BAD_HTTP_STATUS,
|
|
ERROR_CODE_IO_FILE_NOT_FOUND,
|
|
ERROR_CODE_IO_NO_PERMISSION,
|
|
ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED,
|
|
ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE,
|
|
ERROR_CODE_PARSING_CONTAINER_MALFORMED,
|
|
ERROR_CODE_PARSING_MANIFEST_MALFORMED,
|
|
ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED,
|
|
ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED,
|
|
ERROR_CODE_DECODER_INIT_FAILED,
|
|
ERROR_CODE_DECODER_QUERY_FAILED,
|
|
ERROR_CODE_DECODING_FAILED,
|
|
ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES,
|
|
ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
|
|
ERROR_CODE_AUDIO_TRACK_INIT_FAILED,
|
|
ERROR_CODE_AUDIO_TRACK_WRITE_FAILED,
|
|
ERROR_CODE_DRM_UNSPECIFIED,
|
|
ERROR_CODE_DRM_SCHEME_UNSUPPORTED,
|
|
ERROR_CODE_DRM_PROVISIONING_FAILED,
|
|
ERROR_CODE_DRM_CONTENT_ERROR,
|
|
ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED,
|
|
ERROR_CODE_DRM_DISALLOWED_OPERATION,
|
|
ERROR_CODE_DRM_SYSTEM_ERROR,
|
|
ERROR_CODE_DRM_DEVICE_REVOKED,
|
|
ERROR_CODE_DRM_LICENSE_EXPIRED
|
|
})
|
|
public @interface ErrorCode {}
|
|
|
|
// Miscellaneous errors (1xxx).
|
|
|
|
/** Caused by an error whose cause could not be identified. */
|
|
public static final int ERROR_CODE_UNSPECIFIED = 1000;
|
|
/**
|
|
* Caused by an unidentified error in a remote Player, which is a Player that runs on a different
|
|
* host or process.
|
|
*/
|
|
public static final int ERROR_CODE_REMOTE_ERROR = 1001;
|
|
/** Caused by the loading position falling behind the sliding window of available live content. */
|
|
public static final int ERROR_CODE_BEHIND_LIVE_WINDOW = 1002;
|
|
/** Caused by a generic timeout. */
|
|
public static final int ERROR_CODE_TIMEOUT = 1003;
|
|
/**
|
|
* Caused by a failed runtime check.
|
|
*
|
|
* <p>This can happen when the application fails to comply with the player's API requirements (for
|
|
* example, by passing invalid arguments), or when the player reaches an invalid state.
|
|
*/
|
|
public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 1004;
|
|
|
|
// Input/Output errors (2xxx).
|
|
|
|
/** Caused by an Input/Output error which could not be identified. */
|
|
public static final int ERROR_CODE_IO_UNSPECIFIED = 2000;
|
|
/**
|
|
* Caused by a network connection failure.
|
|
*
|
|
* <p>The following is a non-exhaustive list of possible reasons:
|
|
*
|
|
* <ul>
|
|
* <li>There is no network connectivity (you can check this by querying {@link
|
|
* ConnectivityManager#getActiveNetwork}).
|
|
* <li>The URL's domain is misspelled or does not exist.
|
|
* <li>The target host is unreachable.
|
|
* <li>The server unexpectedly closes the connection.
|
|
* </ul>
|
|
*/
|
|
public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 2001;
|
|
/** Caused by a network timeout, meaning the server is taking too long to fulfill a request. */
|
|
public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 2002;
|
|
/**
|
|
* Caused by a server returning a resource with an invalid "Content-Type" HTTP header value.
|
|
*
|
|
* <p>For example, this can happen when the player is expecting a piece of media, but the server
|
|
* returns a paywall HTML page, with content type "text/html".
|
|
*/
|
|
public static final int ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE = 2003;
|
|
/** Caused by an HTTP server returning an unexpected HTTP response status code. */
|
|
public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2004;
|
|
/** Caused by a non-existent file. */
|
|
public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005;
|
|
/**
|
|
* Caused by lack of permission to perform an IO operation. For example, lack of permission to
|
|
* access internet or external storage.
|
|
*/
|
|
public static final int ERROR_CODE_IO_NO_PERMISSION = 2006;
|
|
/**
|
|
* Caused by the player trying to access cleartext HTTP traffic (meaning http:// rather than
|
|
* https://) when the app's Network Security Configuration does not permit it.
|
|
*
|
|
* <p>See <a href="https://exoplayer.dev/issues/cleartext-not-permitted">this corresponding
|
|
* troubleshooting topic</a>.
|
|
*/
|
|
public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2007;
|
|
/** Caused by reading data out of the data bound. */
|
|
public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 2008;
|
|
|
|
// Content parsing errors (3xxx).
|
|
|
|
/** Caused by a parsing error associated with a media container format bitstream. */
|
|
public static final int ERROR_CODE_PARSING_CONTAINER_MALFORMED = 3001;
|
|
/**
|
|
* Caused by a parsing error associated with a media manifest. Examples of a media manifest are a
|
|
* DASH or a SmoothStreaming manifest, or an HLS playlist.
|
|
*/
|
|
public static final int ERROR_CODE_PARSING_MANIFEST_MALFORMED = 3002;
|
|
/**
|
|
* Caused by attempting to extract a file with an unsupported media container format, or an
|
|
* unsupported media container feature.
|
|
*/
|
|
public static final int ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED = 3003;
|
|
/**
|
|
* Caused by an unsupported feature in a media manifest. Examples of a media manifest are a DASH
|
|
* or a SmoothStreaming manifest, or an HLS playlist.
|
|
*/
|
|
public static final int ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED = 3004;
|
|
|
|
// Decoding errors (4xxx).
|
|
|
|
/** Caused by a decoder initialization failure. */
|
|
public static final int ERROR_CODE_DECODER_INIT_FAILED = 4001;
|
|
/** Caused by a decoder query failure. */
|
|
public static final int ERROR_CODE_DECODER_QUERY_FAILED = 4002;
|
|
/** Caused by a failure while trying to decode media samples. */
|
|
public static final int ERROR_CODE_DECODING_FAILED = 4003;
|
|
/** Caused by trying to decode content whose format exceeds the capabilities of the device. */
|
|
public static final int ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES = 4004;
|
|
/** Caused by trying to decode content whose format is not supported. */
|
|
public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005;
|
|
|
|
// AudioTrack errors (5xxx).
|
|
|
|
/** Caused by an AudioTrack initialization failure. */
|
|
public static final int ERROR_CODE_AUDIO_TRACK_INIT_FAILED = 5001;
|
|
/** Caused by an AudioTrack write operation failure. */
|
|
public static final int ERROR_CODE_AUDIO_TRACK_WRITE_FAILED = 5002;
|
|
|
|
// DRM errors (6xxx).
|
|
|
|
/** Caused by an unspecified error related to DRM protection. */
|
|
public static final int ERROR_CODE_DRM_UNSPECIFIED = 6000;
|
|
/**
|
|
* Caused by a chosen DRM protection scheme not being supported by the device. Examples of DRM
|
|
* protection schemes are ClearKey and Widevine.
|
|
*/
|
|
public static final int ERROR_CODE_DRM_SCHEME_UNSUPPORTED = 6001;
|
|
/** Caused by a failure while provisioning the device. */
|
|
public static final int ERROR_CODE_DRM_PROVISIONING_FAILED = 6002;
|
|
/**
|
|
* Caused by attempting to play incompatible DRM-protected content.
|
|
*
|
|
* <p>For example, this can happen when attempting to play a DRM protected stream using a scheme
|
|
* (like Widevine) for which there is no corresponding license acquisition data (like a pssh box).
|
|
*/
|
|
public static final int ERROR_CODE_DRM_CONTENT_ERROR = 6003;
|
|
/** Caused by a failure while trying to obtain a license. */
|
|
public static final int ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED = 6004;
|
|
/** Caused by an operation being disallowed by a license policy. */
|
|
public static final int ERROR_CODE_DRM_DISALLOWED_OPERATION = 6005;
|
|
/** Caused by an error in the DRM system. */
|
|
public static final int ERROR_CODE_DRM_SYSTEM_ERROR = 6006;
|
|
/** Caused by the device having revoked DRM privileges. */
|
|
public static final int ERROR_CODE_DRM_DEVICE_REVOKED = 6007;
|
|
/** Caused by an expired DRM license being loaded into an open DRM session. */
|
|
public static final int ERROR_CODE_DRM_LICENSE_EXPIRED = 6008;
|
|
|
|
/**
|
|
* Player implementations that want to surface custom errors can use error codes greater than this
|
|
* value, so as to avoid collision with other error codes defined in this class.
|
|
*/
|
|
public static final int CUSTOM_ERROR_CODE_BASE = 1000000;
|
|
|
|
/** Returns the name of a given {@code errorCode}. */
|
|
public static String getErrorCodeName(@ErrorCode int errorCode) {
|
|
switch (errorCode) {
|
|
case ERROR_CODE_UNSPECIFIED:
|
|
return "ERROR_CODE_UNSPECIFIED";
|
|
case ERROR_CODE_REMOTE_ERROR:
|
|
return "ERROR_CODE_REMOTE_ERROR";
|
|
case ERROR_CODE_BEHIND_LIVE_WINDOW:
|
|
return "ERROR_CODE_BEHIND_LIVE_WINDOW";
|
|
case ERROR_CODE_TIMEOUT:
|
|
return "ERROR_CODE_TIMEOUT";
|
|
case ERROR_CODE_FAILED_RUNTIME_CHECK:
|
|
return "ERROR_CODE_FAILED_RUNTIME_CHECK";
|
|
case ERROR_CODE_IO_UNSPECIFIED:
|
|
return "ERROR_CODE_IO_UNSPECIFIED";
|
|
case ERROR_CODE_IO_NETWORK_CONNECTION_FAILED:
|
|
return "ERROR_CODE_IO_NETWORK_CONNECTION_FAILED";
|
|
case ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT:
|
|
return "ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT";
|
|
case ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE:
|
|
return "ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE";
|
|
case ERROR_CODE_IO_BAD_HTTP_STATUS:
|
|
return "ERROR_CODE_IO_BAD_HTTP_STATUS";
|
|
case ERROR_CODE_IO_FILE_NOT_FOUND:
|
|
return "ERROR_CODE_IO_FILE_NOT_FOUND";
|
|
case ERROR_CODE_IO_NO_PERMISSION:
|
|
return "ERROR_CODE_IO_NO_PERMISSION";
|
|
case ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED:
|
|
return "ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED";
|
|
case ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE:
|
|
return "ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE";
|
|
case ERROR_CODE_PARSING_CONTAINER_MALFORMED:
|
|
return "ERROR_CODE_PARSING_CONTAINER_MALFORMED";
|
|
case ERROR_CODE_PARSING_MANIFEST_MALFORMED:
|
|
return "ERROR_CODE_PARSING_MANIFEST_MALFORMED";
|
|
case ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED:
|
|
return "ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED";
|
|
case ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED:
|
|
return "ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED";
|
|
case ERROR_CODE_DECODER_INIT_FAILED:
|
|
return "ERROR_CODE_DECODER_INIT_FAILED";
|
|
case ERROR_CODE_DECODER_QUERY_FAILED:
|
|
return "ERROR_CODE_DECODER_QUERY_FAILED";
|
|
case ERROR_CODE_DECODING_FAILED:
|
|
return "ERROR_CODE_DECODING_FAILED";
|
|
case ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES:
|
|
return "ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES";
|
|
case ERROR_CODE_DECODING_FORMAT_UNSUPPORTED:
|
|
return "ERROR_CODE_DECODING_FORMAT_UNSUPPORTED";
|
|
case ERROR_CODE_AUDIO_TRACK_INIT_FAILED:
|
|
return "ERROR_CODE_AUDIO_TRACK_INIT_FAILED";
|
|
case ERROR_CODE_AUDIO_TRACK_WRITE_FAILED:
|
|
return "ERROR_CODE_AUDIO_TRACK_WRITE_FAILED";
|
|
case ERROR_CODE_DRM_UNSPECIFIED:
|
|
return "ERROR_CODE_DRM_UNSPECIFIED";
|
|
case ERROR_CODE_DRM_SCHEME_UNSUPPORTED:
|
|
return "ERROR_CODE_DRM_SCHEME_UNSUPPORTED";
|
|
case ERROR_CODE_DRM_PROVISIONING_FAILED:
|
|
return "ERROR_CODE_DRM_PROVISIONING_FAILED";
|
|
case ERROR_CODE_DRM_CONTENT_ERROR:
|
|
return "ERROR_CODE_DRM_CONTENT_ERROR";
|
|
case ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED:
|
|
return "ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED";
|
|
case ERROR_CODE_DRM_DISALLOWED_OPERATION:
|
|
return "ERROR_CODE_DRM_DISALLOWED_OPERATION";
|
|
case ERROR_CODE_DRM_SYSTEM_ERROR:
|
|
return "ERROR_CODE_DRM_SYSTEM_ERROR";
|
|
case ERROR_CODE_DRM_DEVICE_REVOKED:
|
|
return "ERROR_CODE_DRM_DEVICE_REVOKED";
|
|
case ERROR_CODE_DRM_LICENSE_EXPIRED:
|
|
return "ERROR_CODE_DRM_LICENSE_EXPIRED";
|
|
default:
|
|
if (errorCode >= CUSTOM_ERROR_CODE_BASE) {
|
|
return "custom error code";
|
|
} else {
|
|
return "invalid error code";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Equivalent to {@link PlaybackException#getErrorCodeName(int)
|
|
* PlaybackException.getErrorCodeName(this.errorCode)}.
|
|
*/
|
|
public final String getErrorCodeName() {
|
|
return getErrorCodeName(errorCode);
|
|
}
|
|
|
|
/** An error code which identifies the cause of the playback failure. */
|
|
public final @ErrorCode int errorCode;
|
|
|
|
/** The value of {@link SystemClock#elapsedRealtime()} when this exception was created. */
|
|
public final long timestampMs;
|
|
|
|
/**
|
|
* Creates an instance.
|
|
*
|
|
* @param errorCode A number which identifies the cause of the error. May be one of the {@link
|
|
* ErrorCode ErrorCodes}.
|
|
* @param cause See {@link #getCause()}.
|
|
* @param message See {@link #getMessage()}.
|
|
*/
|
|
public PlaybackException(
|
|
@Nullable String message, @Nullable Throwable cause, @ErrorCode int errorCode) {
|
|
this(message, cause, errorCode, Clock.DEFAULT.elapsedRealtime());
|
|
}
|
|
|
|
/** Creates a new instance using the fields obtained from the given {@link Bundle}. */
|
|
protected PlaybackException(Bundle bundle) {
|
|
this(
|
|
/* message= */ bundle.getString(keyForField(FIELD_STRING_MESSAGE)),
|
|
/* cause= */ getCauseFromBundle(bundle),
|
|
/* errorCode= */ bundle.getInt(
|
|
keyForField(FIELD_INT_ERROR_CODE), /* defaultValue= */ ERROR_CODE_UNSPECIFIED),
|
|
/* timestampMs= */ bundle.getLong(
|
|
keyForField(FIELD_LONG_TIMESTAMP_MS),
|
|
/* defaultValue= */ SystemClock.elapsedRealtime()));
|
|
}
|
|
|
|
/** Creates a new instance using the given values. */
|
|
protected PlaybackException(
|
|
@Nullable String message,
|
|
@Nullable Throwable cause,
|
|
@ErrorCode int errorCode,
|
|
long timestampMs) {
|
|
super(message, cause);
|
|
this.errorCode = errorCode;
|
|
this.timestampMs = timestampMs;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the error data associated to this exception equals the error data associated to
|
|
* {@code other}.
|
|
*
|
|
* <p>Note that this method does not compare the exceptions' stacktraces.
|
|
*/
|
|
@CallSuper
|
|
public boolean errorInfoEquals(@Nullable PlaybackException other) {
|
|
if (this == other) {
|
|
return true;
|
|
}
|
|
if (other == null || getClass() != other.getClass()) {
|
|
return false;
|
|
}
|
|
|
|
@Nullable Throwable thisCause = getCause();
|
|
@Nullable Throwable thatCause = other.getCause();
|
|
if (thisCause != null && thatCause != null) {
|
|
if (!Util.areEqual(thisCause.getMessage(), thatCause.getMessage())) {
|
|
return false;
|
|
}
|
|
if (!Util.areEqual(thisCause.getClass(), thatCause.getClass())) {
|
|
return false;
|
|
}
|
|
} else if (thisCause != null || thatCause != null) {
|
|
return false;
|
|
}
|
|
return errorCode == other.errorCode
|
|
&& Util.areEqual(getMessage(), other.getMessage())
|
|
&& timestampMs == other.timestampMs;
|
|
}
|
|
|
|
// Bundleable implementation.
|
|
|
|
/**
|
|
* Identifiers for fields in a {@link Bundle} which represents a playback exception. Subclasses
|
|
* may use {@link #FIELD_CUSTOM_ID_BASE} to generate more keys using {@link #keyForField(int)}.
|
|
*
|
|
* <p>Note: Changes to the Bundleable implementation must be backwards compatible, so as to avoid
|
|
* breaking communication across different Bundleable implementation versions.
|
|
*/
|
|
@Documented
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef(
|
|
open = true,
|
|
value = {
|
|
FIELD_INT_ERROR_CODE,
|
|
FIELD_LONG_TIMESTAMP_MS,
|
|
FIELD_STRING_MESSAGE,
|
|
FIELD_STRING_CAUSE_CLASS_NAME,
|
|
FIELD_STRING_CAUSE_MESSAGE,
|
|
})
|
|
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;
|
|
|
|
/**
|
|
* Defines a minimum field id value for subclasses to use when implementing {@link #toBundle()}
|
|
* and {@link Bundleable.Creator}.
|
|
*
|
|
* <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative
|
|
* offset on this constant and passing the result to {@link #keyForField(int)}.
|
|
*/
|
|
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;
|
|
|
|
@CallSuper
|
|
@Override
|
|
public Bundle toBundle() {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt(keyForField(FIELD_INT_ERROR_CODE), errorCode);
|
|
bundle.putLong(keyForField(FIELD_LONG_TIMESTAMP_MS), timestampMs);
|
|
bundle.putString(keyForField(FIELD_STRING_MESSAGE), getMessage());
|
|
@Nullable Throwable cause = getCause();
|
|
if (cause != null) {
|
|
bundle.putString(keyForField(FIELD_STRING_CAUSE_CLASS_NAME), cause.getClass().getName());
|
|
bundle.putString(keyForField(FIELD_STRING_CAUSE_MESSAGE), cause.getMessage());
|
|
}
|
|
return bundle;
|
|
}
|
|
|
|
/**
|
|
* Converts the given {@link FieldNumber} to a string which can be used as a field key when
|
|
* implementing {@link #toBundle()} and {@link Bundleable.Creator}.
|
|
*/
|
|
protected static String keyForField(@FieldNumber int field) {
|
|
return Integer.toString(field, Character.MAX_RADIX);
|
|
}
|
|
|
|
// Creates a new {@link Throwable} with possibly {@code null} message.
|
|
@SuppressWarnings("nullness:argument")
|
|
private static Throwable createThrowable(Class<?> clazz, @Nullable String message)
|
|
throws Exception {
|
|
return (Throwable) clazz.getConstructor(String.class).newInstance(message);
|
|
}
|
|
|
|
// Creates a new {@link RemoteException} with possibly {@code null} message.
|
|
@SuppressWarnings("nullness:argument")
|
|
private static RemoteException createRemoteException(@Nullable String message) {
|
|
return new RemoteException(message);
|
|
}
|
|
|
|
@Nullable
|
|
private static Throwable getCauseFromBundle(Bundle bundle) {
|
|
@Nullable String causeClassName = bundle.getString(keyForField(FIELD_STRING_CAUSE_CLASS_NAME));
|
|
@Nullable String causeMessage = bundle.getString(keyForField(FIELD_STRING_CAUSE_MESSAGE));
|
|
@Nullable Throwable cause = null;
|
|
if (!TextUtils.isEmpty(causeClassName)) {
|
|
try {
|
|
Class<?> clazz =
|
|
Class.forName(
|
|
causeClassName, /* initialize= */ true, PlaybackException.class.getClassLoader());
|
|
if (Throwable.class.isAssignableFrom(clazz)) {
|
|
cause = createThrowable(clazz, causeMessage);
|
|
}
|
|
} catch (Throwable e) {
|
|
// There was an error while creating the cause using reflection, do nothing here and let the
|
|
// finally block handle the issue.
|
|
} finally {
|
|
if (cause == null) {
|
|
// The bundle has fields to represent the cause, but we were unable to re-create the
|
|
// exception using reflection. We instantiate a RemoteException to reflect this problem.
|
|
cause = createRemoteException(causeMessage);
|
|
}
|
|
}
|
|
}
|
|
return cause;
|
|
}
|
|
}
|