blob: e358fc19ba06b2e8d12c52830c76c87da94a7bd9 [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.R;
import static org.robolectric.RuntimeEnvironment.castNativePtr;
import android.os.BadParcelableException;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.ReflectionHelpers;
@Implements(Parcel.class)
@SuppressWarnings("unchecked")
public class ShadowParcel {
private static final String TAG = "Parcel";
@RealObject private Parcel realObject;
private static final Map<Long, ByteBuffer> NATIVE_PTR_TO_PARCEL = new LinkedHashMap<>();
private static long nextNativePtr = 1; // this needs to start above 0, which is a magic number to Parcel
@Implementation(maxSdk = JELLY_BEAN_MR1)
@SuppressWarnings("TypeParameterUnusedInFormals")
protected <T extends Parcelable> T readParcelable(ClassLoader loader) {
// prior to JB MR2, readParcelableCreator() is inlined here.
Parcelable.Creator<?> creator = readParcelableCreator(loader);
if (creator == null) {
return null;
}
if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
Parcelable.ClassLoaderCreator<?> classLoaderCreator =
(Parcelable.ClassLoaderCreator<?>) creator;
return (T) classLoaderCreator.createFromParcel(realObject, loader);
}
return (T) creator.createFromParcel(realObject);
}
@HiddenApi
@Implementation(minSdk = JELLY_BEAN_MR2)
public Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
//note: calling `readString` will also consume the string, and increment the data-pointer.
//which is exactly what we need, since we do not call the real `readParcelableCreator`.
final String name = realObject.readString();
if (name == null) {
return null;
}
Parcelable.Creator<?> creator;
try {
// If loader == null, explicitly emulate Class.forName(String) "caller
// classloader" behavior.
ClassLoader parcelableClassLoader =
(loader == null ? getClass().getClassLoader() : loader);
// Avoid initializing the Parcelable class until we know it implements
// Parcelable and has the necessary CREATOR field. http://b/1171613.
Class<?> parcelableClass = Class.forName(name, false /* initialize */,
parcelableClassLoader);
if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
throw new BadParcelableException("Parcelable protocol requires that the "
+ "class implements Parcelable");
}
Field f = parcelableClass.getField("CREATOR");
// this is a fix for JDK8<->Android VM incompatibility:
// Apparently, JDK will not allow access to a public field if its
// class is not visible (private or package-private) from the call-site.
f.setAccessible(true);
if ((f.getModifiers() & Modifier.STATIC) == 0) {
throw new BadParcelableException("Parcelable protocol requires "
+ "the CREATOR object to be static on class " + name);
}
Class<?> creatorType = f.getType();
if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) {
// Fail before calling Field.get(), not after, to avoid initializing
// parcelableClass unnecessarily.
throw new BadParcelableException("Parcelable protocol requires a "
+ "Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
creator = (Parcelable.Creator<?>) f.get(null);
} catch (IllegalAccessException e) {
Log.e(TAG, "Illegal access when unmarshalling: " + name, e);
throw new BadParcelableException(
"IllegalAccessException when unmarshalling: " + name);
} catch (ClassNotFoundException e) {
Log.e(TAG, "Class not found when unmarshalling: " + name, e);
throw new BadParcelableException(
"ClassNotFoundException when unmarshalling: " + name);
} catch (NoSuchFieldException e) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "non-null Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
return creator;
}
@Implementation
protected void writeByteArray(byte[] b, int offset, int len) {
if (b == null) {
realObject.writeInt(-1);
return;
}
Number nativePtr = ReflectionHelpers.getField(realObject, "mNativePtr");
nativeWriteByteArray(nativePtr.longValue(), b, offset, len);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static int nativeDataSize(int nativePtr) {
return nativeDataSize((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeDataSize(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).dataSize();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static int nativeDataAvail(int nativePtr) {
return nativeDataAvail((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeDataAvail(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).dataAvailable();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static int nativeDataPosition(int nativePtr) {
return nativeDataPosition((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeDataPosition(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).dataPosition();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static int nativeDataCapacity(int nativePtr) {
return nativeDataCapacity((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeDataCapacity(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).dataCapacity();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeSetDataSize(int nativePtr, int size) {
nativeSetDataSize((long) nativePtr, size);
}
@Implementation(minSdk = LOLLIPOP)
@SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
protected static void nativeSetDataSize(long nativePtr, int size) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).setDataSize(size);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeSetDataPosition(int nativePtr, int pos) {
nativeSetDataPosition((long) nativePtr, pos);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeSetDataPosition(long nativePtr, int pos) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).setDataPosition(pos);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeSetDataCapacity(int nativePtr, int size) {
nativeSetDataCapacity((long) nativePtr, size);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeSetDataCapacity(long nativePtr, int size) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).setDataCapacity(size);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWriteByteArray(int nativePtr, byte[] b, int offset, int len) {
nativeWriteByteArray((long) nativePtr, b, offset, len);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeByteArray(b, offset, len);
}
// duplicate the writeBlob implementation from latest android, to avoid referencing the
// non-existent-in-JDK java.util.Arrays.checkOffsetAndCount method.
@Implementation(minSdk = M)
protected void writeBlob(byte[] b, int offset, int len) {
if (b == null) {
realObject.writeInt(-1);
return;
}
throwsIfOutOfBounds(b.length, offset, len);
long nativePtr = ReflectionHelpers.getField(realObject, "mNativePtr");
nativeWriteBlob(nativePtr, b, offset, len);
}
private static void throwsIfOutOfBounds(int len, int offset, int count) {
if (len < 0) {
throw new ArrayIndexOutOfBoundsException("Negative length: " + len);
}
if ((offset | count) < 0 || offset > len - count) {
throw new ArrayIndexOutOfBoundsException();
}
}
// nativeWriteBlob was introduced in lollipop, thus no need for a int nativePtr variant
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len) {
nativeWriteByteArray(nativePtr, b, offset, len);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWriteInt(int nativePtr, int val) {
nativeWriteInt((long) nativePtr, val);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteInt(long nativePtr, int val) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeInt(val);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWriteLong(int nativePtr, long val) {
nativeWriteLong((long) nativePtr, val);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteLong(long nativePtr, long val) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeLong(val);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWriteFloat(int nativePtr, float val) {
nativeWriteFloat((long) nativePtr, val);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteFloat(long nativePtr, float val) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeFloat(val);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWriteDouble(int nativePtr, double val) {
nativeWriteDouble((long) nativePtr, val);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteDouble(long nativePtr, double val) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeDouble(val);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWriteString(int nativePtr, String val) {
nativeWriteString((long) nativePtr, val);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteString(long nativePtr, String val) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeString(val);
}
@Implementation(minSdk = R)
protected static void nativeWriteString8(long nativePtr, String val) {
nativeWriteString(nativePtr, val);
}
@Implementation(minSdk = R)
protected static void nativeWriteString16(long nativePtr, String val) {
nativeWriteString(nativePtr, val);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeWriteStrongBinder(int nativePtr, IBinder val) {
nativeWriteStrongBinder((long) nativePtr, val);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteStrongBinder(long nativePtr, IBinder val) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).writeStrongBinder(val);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static byte[] nativeCreateByteArray(int nativePtr) {
return nativeCreateByteArray((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static byte[] nativeCreateByteArray(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readByteArray();
}
// nativeReadBlob was introduced in lollipop, thus no need for a int nativePtr variant
@Implementation(minSdk = LOLLIPOP)
protected static byte[] nativeReadBlob(long nativePtr) {
return nativeCreateByteArray(nativePtr);
}
@Implementation(minSdk = O_MR1)
protected static boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readByteArray(dest, destLen);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static int nativeReadInt(int nativePtr) {
return nativeReadInt((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeReadInt(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readInt();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static long nativeReadLong(int nativePtr) {
return nativeReadLong((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static long nativeReadLong(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readLong();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static float nativeReadFloat(int nativePtr) {
return nativeReadFloat((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static float nativeReadFloat(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readFloat();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static double nativeReadDouble(int nativePtr) {
return nativeReadDouble((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static double nativeReadDouble(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readDouble();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static String nativeReadString(int nativePtr) {
return nativeReadString((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static String nativeReadString(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readString();
}
@Implementation(minSdk = R)
protected static String nativeReadString8(long nativePtr) {
return nativeReadString(nativePtr);
}
@Implementation(minSdk = R)
protected static String nativeReadString16(long nativePtr) {
return nativeReadString(nativePtr);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
protected static IBinder nativeReadStrongBinder(int nativePtr) {
return nativeReadStrongBinder((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static IBinder nativeReadStrongBinder(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).readStrongBinder();
}
@Implementation @HiddenApi
synchronized public static Number nativeCreate() {
long nativePtr = nextNativePtr++;
NATIVE_PTR_TO_PARCEL.put(nativePtr, new ByteBuffer());
return castNativePtr(nativePtr);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeFreeBuffer(int nativePtr) {
nativeFreeBuffer((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
@SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
protected static void nativeFreeBuffer(long nativePtr) {
NATIVE_PTR_TO_PARCEL.get(nativePtr).clear();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeDestroy(int nativePtr) {
nativeDestroy((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeDestroy(long nativePtr) {
NATIVE_PTR_TO_PARCEL.remove(nativePtr);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static byte[] nativeMarshall(int nativePtr) {
return nativeMarshall((long) nativePtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static byte[] nativeMarshall(long nativePtr) {
return NATIVE_PTR_TO_PARCEL.get(nativePtr).toByteArray();
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeUnmarshall(int nativePtr, byte[] data, int offset, int length) {
nativeUnmarshall((long) nativePtr, data, offset, length);
}
@Implementation(minSdk = LOLLIPOP)
@SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
protected static void nativeUnmarshall(long nativePtr, byte[] data, int offset, int length) {
NATIVE_PTR_TO_PARCEL.put(nativePtr, ByteBuffer.fromByteArray(data, offset, length));
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeAppendFrom(int thisNativePtr, int otherNativePtr, int offset, int length) {
nativeAppendFrom((long) thisNativePtr, otherNativePtr, offset, length);
}
@Implementation(minSdk = LOLLIPOP)
@SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
protected static void nativeAppendFrom(
long thisNativePtr, long otherNativePtr, int offset, int length) {
ByteBuffer thisByteBuffer = NATIVE_PTR_TO_PARCEL.get(thisNativePtr);
ByteBuffer otherByteBuffer = NATIVE_PTR_TO_PARCEL.get(otherNativePtr);
thisByteBuffer.appendFrom(otherByteBuffer, offset, length);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWriteInterfaceToken(int nativePtr, String interfaceName) {
nativeWriteInterfaceToken((long) nativePtr, interfaceName);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeWriteInterfaceToken(long nativePtr, String interfaceName) {
// Write StrictMode.ThreadPolicy bits (assume 0 for test).
nativeWriteInt(nativePtr, 0);
nativeWriteString(nativePtr, interfaceName);
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeEnforceInterface(int nativePtr, String interfaceName) {
nativeEnforceInterface((long) nativePtr, interfaceName);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeEnforceInterface(long nativePtr, String interfaceName) {
// Consume StrictMode.ThreadPolicy bits (don't bother setting in test).
nativeReadInt(nativePtr);
String actualInterfaceName = nativeReadString(nativePtr);
if (!Objects.equals(interfaceName, actualInterfaceName)) {
throw new SecurityException("Binder invocation to an incorrect interface");
}
}
private static class ByteBuffer {
// List of elements where a pair is a piece of data and the sizeof that data
private List<Pair<Integer, ?>> buffer = new ArrayList<>();
private int index;
/**
* Removes all elements from the byte buffer
*/
public void clear() {
index = 0;
buffer.clear();
}
/**
* Reads a byte array from the byte buffer based on the current data position
*/
public byte[] readByteArray() {
int length = readInt();
if (length == -1) {
return null;
}
byte[] array = new byte[length];
for (int i = 0; i < length; i++) {
array[i] = readByte();
}
return array;
}
/**
* Reads a byte array from the byte buffer based on the current data position
*/
public boolean readByteArray(byte[] dest, int destLen) {
int length = readInt();
if (length >= 0 && length <= dataAvailable() && length == destLen) {
for (int i = 0; i < length; i++) {
dest[i] = readByte();
}
return true;
}
return false;
}
/**
* Writes a byte to the byte buffer at the current data position
*/
public void writeByte(byte b) {
writeValue(Byte.SIZE / 8, b);
}
/**
* Writes a byte array starting at offset for length bytes to the byte buffer at the current
* data position
*/
public void writeByteArray(byte[] b, int offset, int length) {
writeInt(b.length);
for (int i = offset; i < offset + length && i < b.length; i++) {
writeByte(b[i]);
}
}
/**
* Reads a byte from the byte buffer based on the current data position
*/
public byte readByte() {
return readValue((byte) 0);
}
/**
* Writes an int to the byte buffer at the current data position
*/
public void writeInt(int i) {
writeValue(Integer.SIZE / 8, i);
}
/**
* Reads a int from the byte buffer based on the current data position
*/
public int readInt() {
return readValue(0);
}
/**
* Writes a long to the byte buffer at the current data position
*/
public void writeLong(long l) {
writeValue(Long.SIZE / 8, l);
}
/**
* Reads a long from the byte buffer based on the current data position
*/
public long readLong() {
return readValue(0L);
}
/**
* Writes a float to the byte buffer at the current data position
*/
public void writeFloat(float f) {
writeValue(Float.SIZE / 8, f);
}
/**
* Reads a float from the byte buffer based on the current data position
*/
public float readFloat() {
return readValue(0f);
}
/**
* Writes a double to the byte buffer at the current data position
*/
public void writeDouble(double d) {
writeValue(Double.SIZE / 8, d);
}
/**
* Reads a double from the byte buffer based on the current data position
*/
public double readDouble() {
return readValue(0d);
}
/**
* Writes a String to the byte buffer at the current data position
*/
public void writeString(String s) {
int length = TextUtils.isEmpty(s) ? Integer.SIZE / 8 : s.length();
writeValue(length, s);
}
/**
* Reads a String from the byte buffer based on the current data position
*/
public String readString() {
return readValue(null);
}
/**
* Writes an IBinder to the byte buffer at the current data position
*/
public void writeStrongBinder(IBinder b) {
// Size of struct flat_binder_object in android/binder.h used to encode binders in the real
// parceling code.
int length = 5 * Integer.SIZE / 8;
writeValue(length, b);
}
/**
* Reads an IBinder from the byte buffer based on the current data position
*/
public IBinder readStrongBinder() {
return readValue(null);
}
/**
* Appends the contents of the other byte buffer to this byte buffer
* starting at offset and ending at length.
*
* @param other ByteBuffer to append to this one
* @param offset number of bytes from beginning of byte buffer to start copy from
* @param length number of bytes to copy
*/
public void appendFrom(ByteBuffer other, int offset, int length) {
int otherIndex = other.toIndex(offset);
int otherEndIndex = other.toIndex(offset + length);
for (int i = otherIndex; i < otherEndIndex && i < other.buffer.size(); i++) {
int elementSize = other.buffer.get(i).first;
Object elementValue = other.buffer.get(i).second;
writeValue(elementSize, elementValue);
}
}
/**
* Creates a Byte buffer from a raw byte array.
*
* @param array byte array to read from
* @param offset starting position in bytes to start reading array at
* @param length number of bytes to read from array
*/
public static ByteBuffer fromByteArray(byte[] array, int offset, int length) {
ByteBuffer byteBuffer = new ByteBuffer();
try {
ByteArrayInputStream bis = new ByteArrayInputStream(array, offset,
length);
ObjectInputStream ois = new ObjectInputStream(bis);
int numElements = ois.readInt();
for (int i = 0; i < numElements; i++) {
int sizeOf = ois.readInt();
Object value = ois.readObject();
byteBuffer.buffer.add(Pair.create(sizeOf, value));
}
return byteBuffer;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Converts a ByteBuffer to a raw byte array. This method should be
* symmetrical with fromByteArray.
*/
public byte[] toByteArray() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
int length = buffer.size();
oos.writeInt(length);
for (Pair<Integer, ?> element : buffer) {
oos.writeInt(element.first);
oos.writeObject(element.second);
}
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Number of unused bytes in this byte buffer.
*/
public int dataAvailable() {
return dataSize() - dataPosition();
}
/**
* Total buffer size in bytes of byte buffer included unused space.
*/
public int dataCapacity() {
return dataSize();
}
/**
* Current data position of byte buffer in bytes. Reads / writes are from this position.
*/
public int dataPosition() {
return toDataPosition(index);
}
/**
* Current amount of bytes currently written for ByteBuffer.
*/
public int dataSize() {
int totalSize = totalSize();
int dataPosition = dataPosition();
return totalSize > dataPosition ? totalSize : dataPosition;
}
/**
* Sets the current data position.
*
* @param pos
* Desired position in bytes
*/
public void setDataPosition(int pos) {
index = toIndex(pos);
}
public void setDataSize(int size) {
// TODO
}
public void setDataCapacity(int size) {
// TODO
}
private int totalSize() {
int size = 0;
for (Pair<Integer, ?> element : buffer) {
size += element.first;
}
return size;
}
private <T> T readValue(T defaultValue) {
return (index < buffer.size()) ? (T) buffer.get(index++).second : defaultValue;
}
private void writeValue(int i, Object o) {
// Align the data size to 4-byte boundaries like Parcel does.
final int pad = (4 - (i & 3)) & 3;
if (pad != 0) {
i += pad;
}
Pair<Integer, ?> value = Pair.create(i, o);
if (index < buffer.size()) {
buffer.set(index, value);
} else {
buffer.add(value);
}
index++;
}
private int toDataPosition(int index) {
int pos = 0;
for (int i = 0; i < index; i++) {
pos += buffer.get(i).first;
}
return pos;
}
private int toIndex(int dataPosition) {
int calculatedPos = 0;
int i = 0;
for (; i < buffer.size() && calculatedPos < dataPosition; i++) {
calculatedPos += buffer.get(i).first;
}
return i;
}
}
@Implementation(maxSdk = P)
protected static FileDescriptor openFileDescriptor(String file, int mode) throws IOException {
RandomAccessFile randomAccessFile =
new RandomAccessFile(file, mode == ParcelFileDescriptor.MODE_READ_ONLY ? "r" : "rw");
return randomAccessFile.getFD();
}
}