[go: nahoru, domu]

blob: 92499b68a419a3304836e5c378186043db277192 [file] [log] [blame]
/*
* Copyright 2018 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 androidx.versionedparcelable;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.NetworkOnMainThreadException;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Size;
import android.util.SizeF;
import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.collection.ArrayMap;
import androidx.collection.ArraySet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public abstract class VersionedParcel {
private static final String TAG = "VersionedParcel";
// These constants cannot change once shipped.
private static final int EX_SECURITY = -1;
private static final int EX_BAD_PARCELABLE = -2;
private static final int EX_ILLEGAL_ARGUMENT = -3;
private static final int EX_NULL_POINTER = -4;
private static final int EX_ILLEGAL_STATE = -5;
private static final int EX_NETWORK_MAIN_THREAD = -6;
private static final int EX_UNSUPPORTED_OPERATION = -7;
private static final int EX_PARCELABLE = -9;
private static final int TYPE_VERSIONED_PARCELABLE = 1;
private static final int TYPE_PARCELABLE = 2;
private static final int TYPE_SERIALIZABLE = 3;
private static final int TYPE_STRING = 4;
private static final int TYPE_BINDER = 5;
private static final int TYPE_INTEGER = 7;
private static final int TYPE_FLOAT = 8;
protected final ArrayMap<String, Method> mReadCache;
protected final ArrayMap<String, Method> mWriteCache;
protected final ArrayMap<String, Class> mParcelizerCache;
public VersionedParcel(ArrayMap<String, Method> readCache,
ArrayMap<String, Method> writeCache,
ArrayMap<String, Class> parcelizerCache) {
mReadCache = readCache;
mWriteCache = writeCache;
mParcelizerCache = parcelizerCache;
}
/**
* Whether this VersionedParcel is serializing into a stream and will not accept Parcelables.
*/
public boolean isStream() {
return false;
}
/**
* Closes the last field when done parceling.
*/
protected abstract void closeField();
/**
* Create a sub-parcel to be used for a child VersionedParcelable
*/
protected abstract VersionedParcel createSubParcel();
/**
* Write a byte array into the parcel.
*
* @param b Bytes to place into the parcel.
*/
protected abstract void writeByteArray(byte[] b);
/**
* Write a byte array into the parcel.
*
* @param b Bytes to place into the parcel.
* @param offset Index of first byte to be written.
* @param len Number of bytes to write.
*/
protected abstract void writeByteArray(byte[] b, int offset, int len);
/**
* Write a CharSequence into the parcel.
*/
protected abstract void writeCharSequence(CharSequence charSequence);
/**
* Write an integer value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
protected abstract void writeInt(int val);
/**
* Write a long integer value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
protected abstract void writeLong(long val);
/**
* Write a floating point value into the parcel at the current
* dataPosition(), growing dataCapacity() if needed.
*/
protected abstract void writeFloat(float val);
/**
* Write a double precision floating point value into the parcel at the
* current dataPosition(), growing dataCapacity() if needed.
*/
protected abstract void writeDouble(double val);
/**
* Write a string value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
protected abstract void writeString(String val);
/**
* Write an object into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
protected abstract void writeStrongBinder(IBinder val);
/**
* Flatten the name of the class of the VersionedParcelable and its contents
* into the parcel.
*
* @param p The VersionedParcelable object to be written.
* {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
*/
protected abstract void writeParcelable(Parcelable p);
/**
* Write a boolean value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
protected abstract void writeBoolean(boolean val);
/**
* Write an object into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
protected abstract void writeStrongInterface(IInterface val);
/**
* Flatten a Bundle into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
protected abstract void writeBundle(Bundle val);
/**
* Read an integer value from the parcel at the current dataPosition().
*/
protected abstract int readInt();
/**
* Read a long integer value from the parcel at the current dataPosition().
*/
protected abstract long readLong();
/**
* Read a floating point value from the parcel at the current
* dataPosition().
*/
protected abstract float readFloat();
/**
* Read a double precision floating point value from the parcel at the
* current dataPosition().
*/
protected abstract double readDouble();
/**
* Read a string value from the parcel at the current dataPosition().
*/
protected abstract String readString();
/**
* Read an object from the parcel at the current dataPosition().
*/
protected abstract IBinder readStrongBinder();
/**
* Read a byte[] object from the parcel.
*/
protected abstract byte[] readByteArray();
/**
* Read a CharSequence from the parcel
*/
protected abstract CharSequence readCharSequence();
/**
*/
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
protected abstract <T extends Parcelable> T readParcelable();
/**
* Read and return a new Bundle object from the parcel at the current
* dataPosition(). Returns null if the previously written Bundle object was
* null.
*/
protected abstract Bundle readBundle();
/**
* Read a boolean value from the parcel at the current dataPosition().
*/
protected abstract boolean readBoolean();
/**
* Prepares to read data from a specific field for the following read
* calls.
*/
protected abstract boolean readField(int fieldId);
/**
* Sets the output of write methods to be tagged as part of the specified
* fieldId.
*/
protected abstract void setOutputField(int fieldId);
/**
* Configure the VersionedParcel for current serialization method.
*/
public void setSerializationFlags(boolean allowSerialization, boolean ignoreParcelables) {
// Don't care except in VersionedParcelStream
}
/**
* Write an object into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeStrongInterface(IInterface val, int fieldId) {
setOutputField(fieldId);
writeStrongInterface(val);
}
/**
* Flatten a Bundle into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeBundle(Bundle val, int fieldId) {
setOutputField(fieldId);
writeBundle(val);
}
/**
* Write a boolean value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeBoolean(boolean val, int fieldId) {
setOutputField(fieldId);
writeBoolean(val);
}
/**
* Write a byte array into the parcel.
*
* @param b Bytes to place into the parcel.
*/
public void writeByteArray(byte[] b, int fieldId) {
setOutputField(fieldId);
writeByteArray(b);
}
/**
* Write a byte array into the parcel.
*
* @param b Bytes to place into the parcel.
* @param offset Index of first byte to be written.
* @param len Number of bytes to write.
*/
public void writeByteArray(byte[] b, int offset, int len, int fieldId) {
setOutputField(fieldId);
writeByteArray(b, offset, len);
}
/**
* Write a CharSequence into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeCharSequence(CharSequence val, int fieldId) {
setOutputField(fieldId);
writeCharSequence(val);
}
/**
* Write an integer value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeInt(int val, int fieldId) {
setOutputField(fieldId);
writeInt(val);
}
/**
* Write a long integer value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeLong(long val, int fieldId) {
setOutputField(fieldId);
writeLong(val);
}
/**
* Write a floating point value into the parcel at the current
* dataPosition(), growing dataCapacity() if needed.
*/
public void writeFloat(float val, int fieldId) {
setOutputField(fieldId);
writeFloat(val);
}
/**
* Write a double precision floating point value into the parcel at the
* current dataPosition(), growing dataCapacity() if needed.
*/
public void writeDouble(double val, int fieldId) {
setOutputField(fieldId);
writeDouble(val);
}
/**
* Write a string value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeString(String val, int fieldId) {
setOutputField(fieldId);
writeString(val);
}
/**
* Write an object into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeStrongBinder(IBinder val, int fieldId) {
setOutputField(fieldId);
writeStrongBinder(val);
}
/**
* Flatten the name of the class of the Parcelable and its contents
* into the parcel.
*
* @param p The Parcelable object to be written.
* {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
*/
public void writeParcelable(Parcelable p, int fieldId) {
setOutputField(fieldId);
writeParcelable(p);
}
/**
* Read a boolean value from the parcel at the current dataPosition().
*/
public boolean readBoolean(boolean def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readBoolean();
}
/**
* Read an integer value from the parcel at the current dataPosition().
*/
public int readInt(int def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readInt();
}
/**
* Read a long integer value from the parcel at the current dataPosition().
*/
public long readLong(long def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readLong();
}
/**
* Read a floating point value from the parcel at the current
* dataPosition().
*/
public float readFloat(float def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readFloat();
}
/**
* Read a double precision floating point value from the parcel at the
* current dataPosition().
*/
public double readDouble(double def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readDouble();
}
/**
* Read a string value from the parcel at the current dataPosition().
*/
public String readString(String def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readString();
}
/**
* Read an object from the parcel at the current dataPosition().
*/
public IBinder readStrongBinder(IBinder def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readStrongBinder();
}
/**
* Read a byte[] object from the parcel and copy it into the
* given byte array.
*/
public byte[] readByteArray(byte[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readByteArray();
}
/**
*/
public <T extends Parcelable> T readParcelable(T def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readParcelable();
}
/**
* Read and return a new Bundle object from the parcel at the current
* dataPosition(). Returns null if the previously written Bundle object was
* null.
*/
public Bundle readBundle(Bundle def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readBundle();
}
/**
* Write a byte value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
public void writeByte(byte val, int fieldId) {
setOutputField(fieldId);
writeInt(val);
}
/**
* Flatten a Size into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void writeSize(Size val, int fieldId) {
setOutputField(fieldId);
writeBoolean(val != null);
if (val != null) {
writeInt(val.getWidth());
writeInt(val.getHeight());
}
}
/**
* Flatten a SizeF into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void writeSizeF(SizeF val, int fieldId) {
setOutputField(fieldId);
writeBoolean(val != null);
if (val != null) {
writeFloat(val.getWidth());
writeFloat(val.getHeight());
}
}
/**
*/
public void writeSparseBooleanArray(SparseBooleanArray val, int fieldId) {
setOutputField(fieldId);
if (val == null) {
writeInt(-1);
return;
}
int n = val.size();
writeInt(n);
int i = 0;
while (i < n) {
writeInt(val.keyAt(i));
writeBoolean(val.valueAt(i));
i++;
}
}
/**
*/
public void writeBooleanArray(boolean[] val, int fieldId) {
setOutputField(fieldId);
writeBooleanArray(val);
}
/**
*/
protected void writeBooleanArray(boolean[] val) {
if (val != null) {
int n = val.length;
writeInt(n);
for (int i = 0; i < n; i++) {
writeInt(val[i] ? 1 : 0);
}
} else {
writeInt(-1);
}
}
/**
*/
public boolean[] readBooleanArray(boolean[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readBooleanArray();
}
/**
*/
protected boolean[] readBooleanArray() {
int n = readInt();
if (n < 0) {
return null;
}
boolean[] val = new boolean[n];
for (int i = 0; i < n; i++) {
val[i] = readInt() != 0;
}
return val;
}
/**
*/
public void writeCharArray(char[] val, int fieldId) {
setOutputField(fieldId);
if (val != null) {
int n = val.length;
writeInt(n);
for (int i = 0; i < n; i++) {
writeInt((int) val[i]);
}
} else {
writeInt(-1);
}
}
/**
*/
public CharSequence readCharSequence(CharSequence def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readCharSequence();
}
/**
*/
public char[] readCharArray(char[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
int n = readInt();
if (n < 0) {
return null;
}
char[] val = new char[n];
for (int i = 0; i < n; i++) {
val[i] = (char) readInt();
}
return val;
}
/**
*/
public void writeIntArray(int[] val, int fieldId) {
setOutputField(fieldId);
writeIntArray(val);
}
/**
*/
protected void writeIntArray(int[] val) {
if (val != null) {
int n = val.length;
writeInt(n);
for (int i = 0; i < n; i++) {
writeInt(val[i]);
}
} else {
writeInt(-1);
}
}
/**
*/
public int[] readIntArray(int[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readIntArray();
}
/**
*/
protected int[] readIntArray() {
int n = readInt();
if (n < 0) {
return null;
}
int[] val = new int[n];
for (int i = 0; i < n; i++) {
val[i] = readInt();
}
return val;
}
/**
*/
public void writeLongArray(long[] val, int fieldId) {
setOutputField(fieldId);
writeLongArray(val);
}
/**
*/
protected void writeLongArray(long[] val) {
if (val != null) {
int n = val.length;
writeInt(n);
for (int i = 0; i < n; i++) {
writeLong(val[i]);
}
} else {
writeInt(-1);
}
}
/**
*/
public long[] readLongArray(long[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readLongArray();
}
/**
*/
protected long[] readLongArray() {
int n = readInt();
if (n < 0) {
return null;
}
long[] val = new long[n];
for (int i = 0; i < n; i++) {
val[i] = readLong();
}
return val;
}
/**
*/
public void writeFloatArray(float[] val, int fieldId) {
setOutputField(fieldId);
writeFloatArray(val);
}
/**
*/
protected void writeFloatArray(float[] val) {
if (val != null) {
int n = val.length;
writeInt(n);
for (int i = 0; i < n; i++) {
writeFloat(val[i]);
}
} else {
writeInt(-1);
}
}
/**
*/
public float[] readFloatArray(float[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readFloatArray();
}
/**
*/
protected float[] readFloatArray() {
int n = readInt();
if (n < 0) {
return null;
}
float[] val = new float[n];
for (int i = 0; i < n; i++) {
val[i] = readFloat();
}
return val;
}
/**
*/
public void writeDoubleArray(double[] val, int fieldId) {
setOutputField(fieldId);
writeDoubleArray(val);
}
/**
*/
protected void writeDoubleArray(double[] val) {
if (val != null) {
int n = val.length;
writeInt(n);
for (int i = 0; i < n; i++) {
writeDouble(val[i]);
}
} else {
writeInt(-1);
}
}
/**
*/
public double[] readDoubleArray(double[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readDoubleArray();
}
/**
*/
protected double[] readDoubleArray() {
int n = readInt();
if (n < 0) {
return null;
}
double[] val = new double[n];
for (int i = 0; i < n; i++) {
val[i] = readDouble();
}
return val;
}
/**
* Flatten a Set containing a particular object type into the parcel, at
* the current dataPosition() and growing dataCapacity() if needed. The
* type of the objects in the list must be one that implements VersionedParcelable,
* Parcelable, String, or Serializable.
*
* @param val The list of objects to be written.
* @see #readSet
* @see VersionedParcelable
*/
public <T> void writeSet(Set<T> val, int fieldId) {
writeCollection(val, fieldId);
}
/**
* Flatten a List containing a particular object type into the parcel, at
* the current dataPosition() and growing dataCapacity() if needed. The
* type of the objects in the list must be one that implements VersionedParcelable,
* Parcelable, String, Float, Integer or Serializable.
*
* @param val The list of objects to be written.
* @see #readList
* @see VersionedParcelable
*/
public <T> void writeList(List<T> val, int fieldId) {
writeCollection(val, fieldId);
}
/**
* Flatten a Map containing a particular object type into the parcel, at
* the current dataPosition() and growing dataCapacity() if needed. The
* type of the objects in the list must be one that implements VersionedParcelable,
* Parcelable, String, Float, Integer or Serializable.
*
* @param val The list of objects to be written.
* @see #readMap
* @see VersionedParcelable
*/
public <K, V> void writeMap(Map<K, V> val, int fieldId) {
setOutputField(fieldId);
if (val == null) {
writeInt(-1);
return;
}
int size = val.size();
writeInt(size);
if (size == 0) {
return;
}
List<K> keySet = new ArrayList<>();
List<V> valueSet = new ArrayList<>();
for (Map.Entry<K, V> entry : val.entrySet()) {
keySet.add(entry.getKey());
valueSet.add(entry.getValue());
}
writeCollection(keySet);
writeCollection(valueSet);
}
private <T> void writeCollection(Collection<T> val, int fieldId) {
setOutputField(fieldId);
writeCollection(val);
}
private <T> void writeCollection(Collection<T> val) {
if (val == null) {
writeInt(-1);
return;
}
int n = val.size();
writeInt(n);
if (n > 0) {
int type = getType(val.iterator().next());
writeInt(type);
switch (type) {
case TYPE_STRING:
for (T v : val) {
writeString((String) v);
}
break;
case TYPE_PARCELABLE:
for (T v : val) {
writeParcelable((Parcelable) v);
}
break;
case TYPE_VERSIONED_PARCELABLE:
for (T v : val) {
writeVersionedParcelable((VersionedParcelable) v);
}
break;
case TYPE_SERIALIZABLE:
for (T v : val) {
writeSerializable((Serializable) v);
}
break;
case TYPE_BINDER:
for (T v : val) {
writeStrongBinder((IBinder) v);
}
break;
case TYPE_INTEGER:
for (T v : val) {
writeInt((Integer) v);
}
break;
case TYPE_FLOAT:
for (T v : val) {
writeFloat((Float) v);
}
break;
}
}
}
/**
* Flatten an Array containing a particular object type into the parcel, at
* the current dataPosition() and growing dataCapacity() if needed. The
* type of the objects in the array must be one that implements VersionedParcelable,
* Parcelable, String, or Serializable.
*
* @param val The list of objects to be written.
* @see #readList
* @see VersionedParcelable
*/
public <T> void writeArray(T[] val, int fieldId) {
setOutputField(fieldId);
writeArray(val);
}
/**
*/
protected <T> void writeArray(T[] val) {
if (val == null) {
writeInt(-1);
return;
}
int n = val.length;
int i = 0;
writeInt(n);
if (n > 0) {
int type = getType(val[0]);
writeInt(type);
switch (type) {
case TYPE_STRING:
while (i < n) {
writeString((String) val[i]);
i++;
}
break;
case TYPE_PARCELABLE:
while (i < n) {
writeParcelable((Parcelable) val[i]);
i++;
}
break;
case TYPE_VERSIONED_PARCELABLE:
while (i < n) {
writeVersionedParcelable((VersionedParcelable) val[i]);
i++;
}
break;
case TYPE_SERIALIZABLE:
while (i < n) {
writeSerializable((Serializable) val[i]);
i++;
}
break;
case TYPE_BINDER:
while (i < n) {
writeStrongBinder((IBinder) val[i]);
i++;
}
break;
}
}
}
private <T> int getType(T t) {
if (t instanceof String) {
return TYPE_STRING;
} else if (t instanceof Parcelable) {
return TYPE_PARCELABLE;
} else if (t instanceof VersionedParcelable) {
return TYPE_VERSIONED_PARCELABLE;
} else if (t instanceof Serializable) {
return TYPE_SERIALIZABLE;
} else if (t instanceof IBinder) {
return TYPE_BINDER;
} else if (t instanceof Integer) {
return TYPE_INTEGER;
} else if (t instanceof Float) {
return TYPE_FLOAT;
}
throw new IllegalArgumentException(t.getClass().getName()
+ " cannot be VersionedParcelled");
}
/**
* Flatten the name of the class of the VersionedParcelable and its contents
* into the parcel.
*
* @param p The VersionedParcelable object to be written.
*/
public void writeVersionedParcelable(VersionedParcelable p, int fieldId) {
setOutputField(fieldId);
writeVersionedParcelable(p);
}
/**
*/
protected void writeVersionedParcelable(VersionedParcelable p) {
if (p == null) {
writeString(null);
return;
}
writeVersionedParcelableCreator(p);
VersionedParcel subParcel = createSubParcel();
writeToParcel(p, subParcel);
subParcel.closeField();
}
private void writeVersionedParcelableCreator(VersionedParcelable p) {
Class name = null;
try {
name = findParcelClass(p.getClass());
} catch (ClassNotFoundException e) {
throw new RuntimeException(p.getClass().getSimpleName() + " does not have a Parcelizer",
e);
}
writeString(name.getName());
}
/**
* Write a generic serializable object in to a VersionedParcel. It is strongly
* recommended that this method be avoided, since the serialization
* overhead is extremely large, and this approach will be much slower than
* using the other approaches to writing data in to a VersionedParcel.
*/
public void writeSerializable(Serializable s, int fieldId) {
setOutputField(fieldId);
writeSerializable(s);
}
private void writeSerializable(Serializable s) {
if (s == null) {
writeString(null);
return;
}
String name = s.getClass().getName();
writeString(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(s);
oos.close();
writeByteArray(baos.toByteArray());
} catch (IOException ioe) {
throw new RuntimeException("VersionedParcelable encountered "
+ "IOException writing serializable object (name = " + name
+ ")", ioe);
}
}
/**
* Special function for writing an exception result at the header of
* a parcel, to be used when returning an exception from a transaction.
* Note that this currently only supports a few exception types; any other
* exception will be re-thrown by this function as a RuntimeException
* (to be caught by the system's last-resort exception handling when
* dispatching a transaction).
*
* <p>The supported exception types are:
* <ul>
* <li>{@link BadParcelableException}
* <li>{@link IllegalArgumentException}
* <li>{@link IllegalStateException}
* <li>{@link NullPointerException}
* <li>{@link SecurityException}
* <li>{@link UnsupportedOperationException}
* <li>{@link NetworkOnMainThreadException}
* </ul>
*
* @param e The Exception to be written.
* @see #writeNoException
* @see #readException
*/
public void writeException(Exception e, int fieldId) {
setOutputField(fieldId);
if (e == null) {
writeNoException();
return;
}
int code = 0;
if (e instanceof Parcelable
&& (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
// We only send VersionedParcelable exceptions that are in the
// BootClassLoader to ensure that the receiver can unpack them
code = EX_PARCELABLE;
} else if (e instanceof SecurityException) {
code = EX_SECURITY;
} else if (e instanceof BadParcelableException) {
code = EX_BAD_PARCELABLE;
} else if (e instanceof IllegalArgumentException) {
code = EX_ILLEGAL_ARGUMENT;
} else if (e instanceof NullPointerException) {
code = EX_NULL_POINTER;
} else if (e instanceof IllegalStateException) {
code = EX_ILLEGAL_STATE;
} else if (e instanceof NetworkOnMainThreadException) {
code = EX_NETWORK_MAIN_THREAD;
} else if (e instanceof UnsupportedOperationException) {
code = EX_UNSUPPORTED_OPERATION;
}
writeInt(code);
if (code == 0) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
writeString(e.getMessage());
switch (code) {
case EX_PARCELABLE:
// Write parceled exception prefixed by length
writeParcelable((Parcelable) e);
break;
}
}
/**
* Special function for writing information at the front of the VersionedParcel
* indicating that no exception occurred.
*
* @see #writeException
* @see #readException
*/
protected void writeNoException() {
writeInt(0);
}
/**
* Special function for reading an exception result from the header of
* a parcel, to be used after receiving the result of a transaction. This
* will throw the exception for you if it had been written to the VersionedParcel,
* otherwise return and let you read the normal result data from the VersionedParcel.
*
* @see #writeException
* @see #writeNoException
*/
public Exception readException(Exception def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
int code = readExceptionCode();
if (code != 0) {
String msg = readString();
return readException(code, msg);
}
return def;
}
/**
* Parses the header of a Binder call's response VersionedParcel and
* returns the exception code. Deals with lite or fat headers.
* In the common successful case, this header is generally zero.
* In less common cases, it's a small negative number and will be
* followed by an error string.
*
* This exists purely for android.database.DatabaseUtils and
* insulating it from having to handle fat headers as returned by
* e.g. StrictMode-induced RPC responses.
*/
private int readExceptionCode() {
int code = readInt();
return code;
}
private Exception readException(int code, String msg) {
Exception e = createException(code, msg);
return e;
}
/**
* Gets the root {@link Throwable#getCause() cause} of {@code t}
*/
@NonNull
protected static Throwable getRootCause(@NonNull Throwable t) {
while (t.getCause() != null) t = t.getCause();
return t;
}
/**
* Creates an exception with the given message.
*
* @param code Used to determine which exception class to throw.
* @param msg The exception message.
*/
private Exception createException(int code, String msg) {
switch (code) {
case EX_PARCELABLE:
return (Exception) readParcelable();
case EX_SECURITY:
return new SecurityException(msg);
case EX_BAD_PARCELABLE:
return new BadParcelableException(msg);
case EX_ILLEGAL_ARGUMENT:
return new IllegalArgumentException(msg);
case EX_NULL_POINTER:
return new NullPointerException(msg);
case EX_ILLEGAL_STATE:
return new IllegalStateException(msg);
case EX_NETWORK_MAIN_THREAD:
return new NetworkOnMainThreadException();
case EX_UNSUPPORTED_OPERATION:
return new UnsupportedOperationException(msg);
}
return new RuntimeException("Unknown exception code: " + code
+ " msg " + msg);
}
/**
* Read a byte value from the parcel at the current dataPosition().
*/
public byte readByte(byte def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return (byte) (readInt() & 0xff);
}
/**
* Read a Size from the parcel at the current dataPosition().
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Size readSize(Size def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
if (readBoolean()) {
int width = readInt();
int height = readInt();
return new Size(width, height);
}
return null;
}
/**
* Read a SizeF from the parcel at the current dataPosition().
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public SizeF readSizeF(SizeF def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
if (readBoolean()) {
float width = readFloat();
float height = readFloat();
return new SizeF(width, height);
}
return null;
}
/**
* Read and return a new SparseBooleanArray object from the parcel at the current
* dataPosition(). Returns null if the previously written list object was
* null.
*/
public SparseBooleanArray readSparseBooleanArray(SparseBooleanArray def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
int n = readInt();
if (n < 0) {
return null;
}
SparseBooleanArray sa = new SparseBooleanArray(n);
int i = 0;
while (i < n) {
sa.put(readInt(), readBoolean());
i++;
}
return sa;
}
/**
* Read and return a new ArraySet containing a particular object type from
* the parcel that was written with {@link #writeSet} at the
* current dataPosition(). Returns null if the
* previously written list object was null. The list <em>must</em> have
* previously been written via {@link #writeSet} with the same object
* type.
*
* @return A newly created ArraySet containing objects with the same data
* as those that were previously written.
* @see #writeSet
*/
public <T> Set<T> readSet(Set<T> def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readCollection(new ArraySet<T>());
}
/**
* Read and return a new ArrayList containing a particular object type from
* the parcel that was written with {@link #writeList} at the
* current dataPosition(). Returns null if the
* previously written list object was null. The list <em>must</em> have
* previously been written via {@link #writeList} with the same object
* type.
*
* @return A newly created ArrayList containing objects with the same data
* as those that were previously written.
* @see #writeList
*/
public <T> List<T> readList(List<T> def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readCollection(new ArrayList<T>());
}
@SuppressWarnings("unchecked")
private <T, S extends Collection<T>> S readCollection(S list) {
int n = readInt();
if (n < 0) {
return null;
}
if (n != 0) {
int type = readInt();
if (n < 0) {
return null;
}
switch (type) {
case TYPE_STRING:
while (n > 0) {
list.add((T) readString());
n--;
}
break;
case TYPE_PARCELABLE:
while (n > 0) {
list.add((T) readParcelable());
n--;
}
break;
case TYPE_VERSIONED_PARCELABLE:
while (n > 0) {
list.add((T) readVersionedParcelable());
n--;
}
break;
case TYPE_SERIALIZABLE:
while (n > 0) {
list.add((T) readSerializable());
n--;
}
break;
case TYPE_BINDER:
while (n > 0) {
list.add((T) readStrongBinder());
n--;
}
break;
}
}
return list;
}
/**
* Read and return a new ArrayMap containing a particular object type from
* the parcel that was written with {@link #writeMap} at the
* current dataPosition(). Returns null if the
* previously written list object was null. The list <em>must</em> have
* previously been written via {@link #writeMap} with the same object type.
*
* @return A newly created ArrayMap containing objects with the same data
* as those that were previously written.
* @see #writeMap
*/
public <K, V> Map<K, V> readMap(Map<K, V> def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
int size = readInt();
if (size < 0) {
return null;
}
Map<K, V> map = new ArrayMap<>();
if (size == 0) {
return map;
}
List<K> keyList = new ArrayList<>();
List<V> valueList = new ArrayList<>();
readCollection(keyList);
readCollection(valueList);
for (int i = 0; i < size; i++) {
map.put(keyList.get(i), valueList.get(i));
}
return map;
}
/**
* Read and return a new ArrayList containing a particular object type from
* the parcel that was written with {@link #writeArray} at the
* current dataPosition(). Returns null if the
* previously written list object was null. The list <em>must</em> have
* previously been written via {@link #writeArray} with the same object
* type.
*
* @return A newly created ArrayList containing objects with the same data
* as those that were previously written.
* @see #writeArray
*/
public <T> T[] readArray(T[] def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readArray(def);
}
/**
*/
@SuppressWarnings("unchecked")
protected <T> T[] readArray(T[] def) {
int n = readInt();
if (n < 0) {
return null;
}
ArrayList<T> list = new ArrayList<T>(n);
if (n != 0) {
int type = readInt();
if (n < 0) {
return null;
}
switch (type) {
case TYPE_STRING:
while (n > 0) {
list.add((T) readString());
n--;
}
break;
case TYPE_PARCELABLE:
while (n > 0) {
list.add((T) readParcelable());
n--;
}
break;
case TYPE_VERSIONED_PARCELABLE:
while (n > 0) {
list.add((T) readVersionedParcelable());
n--;
}
break;
case TYPE_SERIALIZABLE:
while (n > 0) {
list.add((T) readSerializable());
n--;
}
break;
case TYPE_BINDER:
while (n > 0) {
list.add((T) readStrongBinder());
n--;
}
break;
}
}
return list.toArray(def);
}
/**
*/
public <T extends VersionedParcelable> T readVersionedParcelable(T def, int fieldId) {
if (!readField(fieldId)) {
return def;
}
return readVersionedParcelable();
}
/**
* Read and return a new VersionedParcelable from the parcel.
*
* @return Returns the newly created VersionedParcelable, or null if a null
* object has been written.
* @throws BadParcelableException Throws BadVersionedParcelableException if there
* was an error trying to instantiate the VersionedParcelable.
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
protected <T extends VersionedParcelable> T readVersionedParcelable() {
String name = readString();
if (name == null) {
return null;
}
return readFromParcel(name, createSubParcel());
}
/**
* Read and return a new Serializable object from the parcel.
*
* @return the Serializable object, or null if the Serializable name
* wasn't found in the parcel.
*/
protected Serializable readSerializable() {
String name = readString();
if (name == null) {
// For some reason we were unable to read the name of the Serializable (either there
// is nothing left in the VersionedParcel to read, or the next value wasn't a String)
// , so
// return null, which indicates that the name wasn't found in the parcel.
return null;
}
byte[] serializedData = readByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
try {
ObjectInputStream ois = new ObjectInputStream(bais) {
@Override
protected Class<?> resolveClass(ObjectStreamClass osClass)
throws IOException, ClassNotFoundException {
Class<?> c = Class.forName(osClass.getName(), false,
getClass().getClassLoader());
if (c != null) {
return c;
}
return super.resolveClass(osClass);
}
};
return (Serializable) ois.readObject();
} catch (IOException ioe) {
throw new RuntimeException("VersionedParcelable encountered "
+ "IOException reading a Serializable object (name = " + name
+ ")", ioe);
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException("VersionedParcelable encountered "
+ "ClassNotFoundException reading a Serializable object (name = "
+ name + ")", cnfe);
}
}
/**
*/
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
protected <T extends VersionedParcelable> T readFromParcel(
String parcelCls, VersionedParcel versionedParcel) {
try {
Method m = getReadMethod(parcelCls);
return (T) m.invoke(null, versionedParcel);
} catch (IllegalAccessException e) {
throw new RuntimeException("VersionedParcel encountered IllegalAccessException", e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new RuntimeException("VersionedParcel encountered InvocationTargetException", e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("VersionedParcel encountered NoSuchMethodException", e);
} catch (ClassNotFoundException e) {
throw new RuntimeException("VersionedParcel encountered ClassNotFoundException", e);
}
}
/**
*/
protected <T extends VersionedParcelable> void writeToParcel(T val,
VersionedParcel versionedParcel) {
try {
Method m = getWriteMethod(val.getClass());
m.invoke(null, val, versionedParcel);
} catch (IllegalAccessException e) {
throw new RuntimeException("VersionedParcel encountered IllegalAccessException", e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new RuntimeException("VersionedParcel encountered InvocationTargetException", e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("VersionedParcel encountered NoSuchMethodException", e);
} catch (ClassNotFoundException e) {
throw new RuntimeException("VersionedParcel encountered ClassNotFoundException", e);
}
}
private Method getReadMethod(String parcelCls) throws IllegalAccessException,
NoSuchMethodException, ClassNotFoundException {
Method m = mReadCache.get(parcelCls);
if (m == null) {
Class<?> cls = Class.forName(parcelCls, true, VersionedParcel.class.getClassLoader());
m = cls.getDeclaredMethod("read", VersionedParcel.class);
mReadCache.put(parcelCls, m);
}
return m;
}
private Method getWriteMethod(Class baseCls) throws IllegalAccessException,
NoSuchMethodException, ClassNotFoundException {
Method m = mWriteCache.get(baseCls.getName());
if (m == null) {
Class<?> cls = findParcelClass(baseCls);
m = cls.getDeclaredMethod("write", baseCls, VersionedParcel.class);
mWriteCache.put(baseCls.getName(), m);
}
return m;
}
private Class findParcelClass(Class<?> cls)
throws ClassNotFoundException {
Class ret = mParcelizerCache.get(cls.getName());
if (ret == null) {
String pkg = cls.getPackage().getName();
String c = String.format("%s.%sParcelizer", pkg, cls.getSimpleName());
ret = Class.forName(c, false, cls.getClassLoader());
mParcelizerCache.put(cls.getName(), ret);
}
return ret;
}
/**
*/
public static class ParcelException extends RuntimeException {
public ParcelException(Throwable source) {
super(source);
}
}
}