package com.xtremelabs.robolectric.shadows; import static com.xtremelabs.robolectric.Robolectric.shadowOf; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import com.xtremelabs.robolectric.Robolectric; import com.xtremelabs.robolectric.bytecode.ShadowWrangler; import com.xtremelabs.robolectric.internal.Implementation; import com.xtremelabs.robolectric.internal.Implements; import com.xtremelabs.robolectric.internal.RealObject; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @Implements(Parcel.class) @SuppressWarnings("unchecked") public class ShadowParcel { private static final int VAL_NULL = -1; private static final int VAL_STRING = 0; private static final int VAL_INTEGER = 1; private static final int VAL_MAP = 2; private static final int VAL_BUNDLE = 3; private static final int VAL_PARCELABLE = 4; private static final int VAL_SHORT = 5; private static final int VAL_LONG = 6; private static final int VAL_FLOAT = 7; private static final int VAL_DOUBLE = 8; private static final int VAL_BOOLEAN = 9; private static final int VAL_CHARSEQUENCE = 10; private static final int VAL_LIST = 11; private static final int VAL_BYTEARRAY = 13; private static final int VAL_STRINGARRAY = 14; private static final int VAL_PARCELABLEARRAY = 16; private static final int VAL_OBJECTARRAY = 17; private static final int VAL_INTARRAY = 18; private static final int VAL_LONGARRAY = 19; private static final int VAL_BYTE = 20; private static final int VAL_BOOLEANARRAY = 23; private static final int VAL_CHARSEQUENCEARRAY = 24; private final ArrayList> parcelData = new ArrayList>(); private int index = 0; @RealObject private Parcel realParcel; @Implementation public static Parcel obtain() { return Robolectric.newInstanceOf(Parcel.class); } @Implementation public int dataAvail() { return dataSize() - dataPosition(); } @Implementation public int dataPosition() { return calculateSizeToIndex(index); } @Implementation public int dataSize() { return calculateSizeToIndex(parcelData.size()); } @Implementation public int dataCapacity() { return dataSize(); } @Implementation public void setDataPosition(int pos) { index = calculateIndexFromSizePosition(pos); } private int calculateSizeToIndex(int index) { int size = 0; for (int i = 0; i < index; i++) { size += parcelData.get(i).first; } return size; } private int calculateIndexFromSizePosition(int pos) { int size = 0; for (int i = 0; i < parcelData.size(); i++) { if (size >= pos) { return i; } size += parcelData.get(i).first; } return parcelData.size(); } @Implementation public void writeString(String str) { if (str == null) { writeInt(-1); } else { writeInt(str.length()); addValueToList(Pair.create(str.length(), str)); } } @Implementation public String readString() { int N = readInt(); if (N < 0) { return null; } else { return readValueFromList(null); } } @Implementation public CharSequence readCharSequence() { return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(realParcel); } @Implementation public void writeCharSequence(CharSequence val) { TextUtils.writeToParcel(val, realParcel, 0); } @Implementation public void writeInt(int i) { addValueToList(Pair.create(Integer.SIZE / 8, i)); } @Implementation public int readInt() { return readValueFromList(0); } @Implementation public void writeLong(long i) { addValueToList(Pair.create(Long.SIZE / 8, i)); } @Implementation public long readLong() { return readValueFromList((long) 0); } @Implementation public void writeFloat(float f) { addValueToList(Pair.create(Float.SIZE / 8, f)); } @Implementation public float readFloat() { return readValueFromList((float) 0); } @Implementation public void writeDouble(double f) { addValueToList(Pair.create(Double.SIZE / 8, f)); } @Implementation public double readDouble() { return readValueFromList((double) 0); } public void writeBoolean(boolean b) { addValueToList(Pair.create(1, b)); } public boolean readBoolean() { return readValueFromList(false); } public void writeChar(char c) { addValueToList(Pair.create(Character.SIZE / 8, c)); } public char readChar() { return readValueFromList((char) 0); } @Implementation @SuppressWarnings("unchecked") public void writeByte(byte b) { addValueToList(Pair.create(Byte.SIZE / 8, b)); } @Implementation public byte readByte() { return readValueFromList((byte) 0); } @Implementation public void readBooleanArray(boolean[] val) { int N = readInt(); if (val.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < val.length; i++) { val[i] = readBoolean(); } } @Implementation public void writeBooleanArray(boolean[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (boolean b : val) writeBoolean(b); } @Implementation public boolean[] createBooleanArray() { int N = readInt(); if (N < 0) { return null; } boolean[] val = new boolean[N]; for (int i = 0; i < val.length; i++) { val[i] = readBoolean(); } return val; } @Implementation public void readCharArray(char[] val) { int N = readInt(); if (val.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < val.length; i++) { val[i] = readChar(); } } @Implementation public void writeCharArray(char[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (char b : val) writeChar(b); } @Implementation public char[] createCharArray() { int N = readInt(); if (N < 0) { return null; } char[] val = new char[N]; for (int i = 0; i < val.length; i++) { val[i] = readChar(); } return val; } @Implementation public void readFloatArray(float[] val) { int N = readInt(); if (val.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < val.length; i++) { val[i] = readFloat(); } } @Implementation public void writeFloatArray(float[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (float f : val) writeFloat(f); } @Implementation public float[] createFloatArray() { int N = readInt(); if (N < 0) { return null; } float[] val = new float[N]; for (int i = 0; i < val.length; i++) { val[i] = readFloat(); } return val; } @Implementation public void writeDoubleArray(double[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (double f : val) writeDouble(f); } @Implementation public void readDoubleArray(double[] val) { int N = readInt(); if (val.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < val.length; i++) { val[i] = readDouble(); } } @Implementation public double[] createDoubleArray() { int N = readInt(); if (N < 0) { return null; } double[] val = new double[N]; for (int i = 0; i < val.length; i++) { val[i] = readDouble(); } return val; } @Implementation public void writeIntArray(int[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (int f : val) writeInt(f); } @Implementation public void readIntArray(int[] val) { int N = readInt(); if (val.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < val.length; i++) { val[i] = readInt(); } } @Implementation public int[] createIntArray() { int N = readInt(); if (N < 0) { return null; } int[] val = new int[N]; for (int i = 0; i < val.length; i++) { val[i] = readInt(); } return val; } @Implementation public void writeByteArray(byte[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (byte f : val) writeByte(f); } @Implementation public void readByteArray(byte[] val) { int N = readInt(); if (val.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < val.length; i++) { val[i] = readByte(); } } @Implementation public byte[] createByteArray() { int N = readInt(); if (N < 0) { return null; } byte[] val = new byte[N]; for (int i = 0; i < val.length; i++) { val[i] = readByte(); } return val; } @Implementation public void writeLongArray(long[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (long f : val) writeLong(f); } @Implementation public void readLongArray(long[] val) { int N = readInt(); if (val.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < val.length; i++) { val[i] = readLong(); } } @Implementation public long[] createLongArray() { int N = readInt(); if (N < 0) { return null; } long[] val = new long[N]; for (int i = 0; i < val.length; i++) { val[i] = readLong(); } return val; } @Implementation public void writeStringArray(String[] val) { if (val == null) { writeInt(-1); return; } writeInt(val.length); for (String f : val) writeString(f); } @Implementation public String[] createStringArray() { String[] array = null; int N = readInt(); if (N >= 0) { array = new String[N]; for (int i = 0; i < N; i++) { array[i] = readString(); } } return array; } @Implementation public void readStringArray(String[] dest) { int N = readInt(); if (dest.length != N) throw new RuntimeException("bad array lengths"); for (int i = 0; i < dest.length; i++) { dest[i] = readString(); } } @Implementation public void writeStringList(List strings) { if (strings == null) { writeInt(-1); return; } int count = strings.size(); int i = 0; writeInt(count); while (i < count) { writeString(strings.get(i)); i++; } } @Implementation public void readStringList(List list) { int listSizeBeforeChange = list.size(); int addCount = readInt(); int i = 0; for (; i < listSizeBeforeChange && i < addCount; i++) { list.set(i, readString()); } for (; i < addCount; i++) { list.add(readString()); } for (; i < listSizeBeforeChange; i++) { list.remove(addCount); } } @Implementation public ArrayList createStringArrayList() { int N = readInt(); if (N < 0) { return null; } ArrayList l = new ArrayList(N); while (N > 0) { l.add(readString()); N--; } return l; } @Implementation public void writeCharSequenceArray(CharSequence[] val) { if (val != null) { int N = val.length; writeInt(N); for (int i=0; i= 0) { array = new CharSequence[length]; for (int i = 0 ; i < length ; i++) { array[i] = readCharSequence(); } } return array; } @Implementation public void writeList(List val) { if (val == null) { writeInt(-1); return; } int N = val.size(); int i = 0; writeInt(N); while (i < N) { writeValue(val.get(i)); i++; } } @Implementation public void readList(List outVal, ClassLoader loader) { int N = readInt(); readListInternal(outVal, N, loader); } @Implementation public ArrayList readArrayList(ClassLoader loader) { int N = readInt(); if (N < 0) { return null; } ArrayList l = new ArrayList(N); readListInternal(l, N, loader); return l; } @Implementation public void writeArray(Object[] values) { if (values == null) { writeInt(-1); return; } int N = values.length; writeInt(N); for (Object value : values) { writeValue(value); } } @Implementation public Object[] readArray(ClassLoader loader) { int N = readInt(); if (N < 0) { return null; } Object[] l = new Object[N]; readArrayInternal(l, N, loader); return l; } @Implementation public void writeValue(Object v) { if (v == null) { writeInt(VAL_NULL); } else if (v instanceof String) { writeInt(VAL_STRING); writeString((String) v); } else if (v instanceof Integer) { writeInt(VAL_INTEGER); writeInt((Integer) v); } else if (v instanceof Map) { writeInt(VAL_MAP); writeMap((Map) v); } else if (v instanceof Bundle) { // Must be before Parcelable writeInt(VAL_BUNDLE); writeBundle((Bundle) v); } else if (v instanceof Parcelable) { writeInt(VAL_PARCELABLE); writeParcelable((Parcelable) v, 0); } else if (v instanceof Short) { writeInt(VAL_SHORT); writeInt(((Short) v).intValue()); } else if (v instanceof Long) { writeInt(VAL_LONG); writeLong((Long) v); } else if (v instanceof Float) { writeInt(VAL_FLOAT); writeFloat((Float) v); } else if (v instanceof Double) { writeInt(VAL_DOUBLE); writeDouble((Double) v); } else if (v instanceof Boolean) { writeInt(VAL_BOOLEAN); writeInt((Boolean) v ? 1 : 0); } else if (v instanceof CharSequence) { // Must be after String writeInt(VAL_CHARSEQUENCE); writeCharSequence((CharSequence) v); } else if (v instanceof List) { writeInt(VAL_LIST); writeList((List) v); } else if (v instanceof boolean[]) { writeInt(VAL_BOOLEANARRAY); writeBooleanArray((boolean[]) v); } else if (v instanceof byte[]) { writeInt(VAL_BYTEARRAY); writeByteArray((byte[]) v); } else if (v instanceof String[]) { writeInt(VAL_STRINGARRAY); writeStringArray((String[]) v); } else if (v instanceof CharSequence[]) { // Must be after String[] and before Object[] writeInt(VAL_CHARSEQUENCEARRAY); writeCharSequenceArray((CharSequence[]) v); } else if (v instanceof Parcelable[]) { writeInt(VAL_PARCELABLEARRAY); writeParcelableArray((Parcelable[]) v, 0); } else if (v instanceof Object[]) { writeInt(VAL_OBJECTARRAY); writeArray((Object[]) v); } else if (v instanceof int[]) { writeInt(VAL_INTARRAY); writeIntArray((int[]) v); } else if (v instanceof long[]) { writeInt(VAL_LONGARRAY); writeLongArray((long[]) v); } else if (v instanceof Byte) { writeInt(VAL_BYTE); writeByte((Byte) v); } else { throw new RuntimeException( "Parcel: unable to marshal value with type" + v.getClass().getName()); } } @Implementation public Object readValue(ClassLoader loader) { int type = readInt(); switch (type) { case VAL_NULL: return null; case VAL_STRING: return readString(); case VAL_INTEGER: return readInt(); case VAL_MAP: return readHashMap(loader); case VAL_PARCELABLE: return readParcelable(loader); case VAL_SHORT: return (short) readInt(); case VAL_LONG: return readLong(); case VAL_FLOAT: return readFloat(); case VAL_DOUBLE: return readDouble(); case VAL_BOOLEAN: return readInt() == 1; case VAL_CHARSEQUENCE: return readCharSequence(); case VAL_LIST: return readArrayList(loader); case VAL_BOOLEANARRAY: return createBooleanArray(); case VAL_BYTEARRAY: return createByteArray(); case VAL_STRINGARRAY: return createStringArray(); case VAL_CHARSEQUENCEARRAY: return readCharSequenceArray(); case VAL_OBJECTARRAY: return readArray(loader); case VAL_INTARRAY: return createIntArray(); case VAL_LONGARRAY: return createLongArray(); case VAL_BYTE: return readByte(); case VAL_PARCELABLEARRAY: return readParcelableArray(loader); case VAL_BUNDLE: return readBundle(loader); // loading will be deferred default: int off = dataPosition() - 4; throw new RuntimeException( "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off); } } @Implementation public Bundle readBundle() { return readBundle(null); } @Implementation public Bundle readBundle(ClassLoader loader) { int offset = dataPosition(); int N = readInt(); if (N < 0) { return null; } int magic = readInt(); if (magic != 0x4C444E42) { throw new RuntimeException("Magic number missing from bundle stream"); } Bundle bundle = new Bundle(); // Read map HashMap m = new HashMap(); readMap(m, null); shadowOf(bundle).map.putAll(m); return bundle; } @Implementation public void writeBundle(Bundle val) { if (val == null) { writeInt(-1); return; } writeInt(-1); // dummy, will hold length int oldPos = dataPosition(); writeInt(0x4C444E42); // 'B' 'N' 'D' 'L' writeMapInternal(shadowOf(val).map); int newPos = dataPosition(); // Backpatch length setDataPosition(oldPos - 4); int N = newPos - oldPos; writeInt(N); setDataPosition(newPos); } @Implementation public void writeParcelable(Parcelable p, int flags) { if (p == null) { writeString(null); return; } String name = p.getClass().getName(); writeString(name); p.writeToParcel(realParcel, flags); } @Implementation public T readParcelable(ClassLoader loader) { String name = readString(); if (name == null) { return null; } Parcelable.Creator creator; try { Class c = loader == null ? Class.forName(name) : Class.forName(name, true, loader); Field f = c.getField("CREATOR"); creator = (Parcelable.Creator) f.get(null); } catch (IllegalAccessException e) { Log.e("Parcel", "Class not found when unmarshalling: " + name + ", e: " + e); throw new RuntimeException("IllegalAccessException when unmarshalling: " + name); } catch (ClassNotFoundException e) { Log.e("Parcel", "Class not found when unmarshalling: " + name + ", e: " + e); throw new RuntimeException("ClassNotFoundException when unmarshalling: " + name); } catch (ClassCastException e) { throw new RuntimeException("Parcelable protocol requires a " + "Parcelable.Creator object called " + " CREATOR on class " + name); } catch (NoSuchFieldException e) { throw new RuntimeException("Parcelable protocol requires a " + "Parcelable.Creator object called " + " CREATOR on class " + name); } if (creator == null) { throw new RuntimeException("Parcelable protocol requires a " + "Parcelable.Creator object called " + " CREATOR on class " + name); } return creator.createFromParcel(realParcel); } @Implementation public ArrayList createTypedArrayList(Parcelable.Creator c) { int N = readInt(); if (N < 0) { return null; } ArrayList l = new ArrayList(N); while (N > 0) { if (readInt() != 0) { l.add(c.createFromParcel(realParcel)); } else { l.add(null); } N--; } return l; } @Implementation public void writeTypedList(List val) { if (val == null) { writeInt(-1); return; } int N = val.size(); int i = 0; writeInt(N); while (i < N) { Object item = val.get(i); if (item != null) { writeInt(1); ((Parcelable) item).writeToParcel(realParcel, 0); } else { writeInt(0); } i++; } } @Implementation public void writeParcelableArray(T[] value, int parcelableFlags) { if (value != null) { int N = value.length; writeInt(N); for (int i=0; i val) { if (val == null) { writeInt(-1); return; } Set> entries = val.entrySet(); writeInt(entries.size()); for (Map.Entry e : entries) { writeValue(e.getKey()); writeValue(e.getValue()); } } private void readMapInternal(Map outVal, int N, ClassLoader loader) { for (int i = 0; i < N; i++) { Object key = readValue(loader); Object value = readValue(loader); outVal.put(key, value); } } private void readListInternal(List outVal, int N, ClassLoader loader) { while (N > 0) { Object value = readValue(loader); outVal.add(value); N--; } } private void readArrayInternal(Object[] outVal, int N, ClassLoader loader) { for (int i = 0; i < N; i++) { Object value = readValue(loader); outVal[i] = value; } } private void addValueToList(Pair value) { if (index < parcelData.size()) { parcelData.set(index, value); } else { parcelData.add(value); } index++; } private T readValueFromList(T defaultValue) { if (index < parcelData.size()) { return (T) parcelData.get(index++).second; } else { return defaultValue; } } public int getIndex() { return index; } public List getParcelData() { return parcelData; } }