VersionedParcel.java

/*
 * 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>());
    }

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

    /**
     */
    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({"unchecked", "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) {
            long start = System.currentTimeMillis();
            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);
            long start = System.currentTimeMillis();
            m = cls.getDeclaredMethod("write", baseCls, VersionedParcel.class);
            mWriteCache.put(baseCls.getName(), m);
        }
        return m;
    }

    private Class findParcelClass(Class<? extends VersionedParcelable> 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);
        }
    }
}