/*
* Copyright (C) 2014 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 android.hardware.camera2.marshal.impl;
import android.hardware.camera2.marshal.Marshaler;
import android.hardware.camera2.marshal.MarshalQueryable;
import android.hardware.camera2.utils.TypeReference;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
/**
* Marshal any {@code T extends Parcelable} to/from any native type
*
*
Use with extreme caution! File descriptors and binders will not be marshaled across.
*/
public class MarshalQueryableParcelable
implements MarshalQueryable {
private static final String TAG = "MarshalParcelable";
private static final boolean DEBUG = false;
private static final String FIELD_CREATOR = "CREATOR";
private class MarshalerParcelable extends Marshaler {
private final Class mClass;
private final Parcelable.Creator mCreator;
@SuppressWarnings("unchecked")
protected MarshalerParcelable(TypeReference typeReference,
int nativeType) {
super(MarshalQueryableParcelable.this, typeReference, nativeType);
mClass = (Class)typeReference.getRawType();
Field creatorField;
try {
creatorField = mClass.getDeclaredField(FIELD_CREATOR);
} catch (NoSuchFieldException e) {
// Impossible. All Parcelable implementations must have a 'CREATOR' static field
throw new AssertionError(e);
}
try {
mCreator = (Parcelable.Creator)creatorField.get(null);
} catch (IllegalAccessException e) {
// Impossible: All 'CREATOR' static fields must be public
throw new AssertionError(e);
} catch (IllegalArgumentException e) {
// Impossible: This is a static field, so null must be ok
throw new AssertionError(e);
}
}
@Override
public void marshal(T value, ByteBuffer buffer) {
if (DEBUG) {
Log.v(TAG, "marshal " + value);
}
Parcel parcel = Parcel.obtain();
byte[] parcelContents;
try {
value.writeToParcel(parcel, /*flags*/0);
if (parcel.hasFileDescriptors()) {
throw new UnsupportedOperationException(
"Parcelable " + value + " must not have file descriptors");
}
parcelContents = parcel.marshall();
}
finally {
parcel.recycle();
}
if (parcelContents.length == 0) {
throw new AssertionError("No data marshaled for " + value);
}
buffer.put(parcelContents);
}
@Override
public T unmarshal(ByteBuffer buffer) {
if (DEBUG) {
Log.v(TAG, "unmarshal, buffer remaining " + buffer.remaining());
}
/*
* Quadratically slow when marshaling an array of parcelables.
*
* Read out the entire byte buffer as an array, then copy it into the parcel.
*
* Once we unparcel the entire object, advance the byte buffer by only how many
* bytes the parcel actually used up.
*
* Future: If we ever do need to use parcelable arrays, we can do this a little smarter
* by reading out a chunk like 4,8,16,24 each time, but not sure how to detect
* parcels being too short in this case.
*
* Future: Alternatively use Parcel#obtain(long) directly into the native
* pointer of a ByteBuffer, which would not copy if the ByteBuffer was direct.
*/
buffer.mark();
Parcel parcel = Parcel.obtain();
try {
int maxLength = buffer.remaining();
byte[] remaining = new byte[maxLength];
buffer.get(remaining);
parcel.unmarshall(remaining, /*offset*/0, maxLength);
parcel.setDataPosition(/*pos*/0);
T value = mCreator.createFromParcel(parcel);
int actualLength = parcel.dataPosition();
if (actualLength == 0) {
throw new AssertionError("No data marshaled for " + value);
}
// set the position past the bytes the parcelable actually used
buffer.reset();
buffer.position(buffer.position() + actualLength);
if (DEBUG) {
Log.v(TAG, "unmarshal, parcel length was " + actualLength);
Log.v(TAG, "unmarshal, value is " + value);
}
return mClass.cast(value);
} finally {
parcel.recycle();
}
}
@Override
public int getNativeSize() {
return NATIVE_SIZE_DYNAMIC;
}
@Override
public int calculateMarshalSize(T value) {
Parcel parcel = Parcel.obtain();
try {
value.writeToParcel(parcel, /*flags*/0);
int length = parcel.marshall().length;
if (DEBUG) {
Log.v(TAG, "calculateMarshalSize, length when parceling "
+ value + " is " + length);
}
return length;
} finally {
parcel.recycle();
}
}
}
@Override
public Marshaler createMarshaler(TypeReference managedType, int nativeType) {
return new MarshalerParcelable(managedType, nativeType);
}
@Override
public boolean isTypeMappingSupported(TypeReference managedType, int nativeType) {
return Parcelable.class.isAssignableFrom(managedType.getRawType());
}
}