1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.hardware.camera2.marshal.impl;
17
18import android.hardware.camera2.marshal.Marshaler;
19import android.hardware.camera2.marshal.MarshalQueryable;
20import android.hardware.camera2.utils.TypeReference;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.Log;
24
25import java.lang.reflect.Field;
26import java.nio.ByteBuffer;
27
28/**
29 * Marshal any {@code T extends Parcelable} to/from any native type
30 *
31 * <p>Use with extreme caution! File descriptors and binders will not be marshaled across.</p>
32 */
33public class MarshalQueryableParcelable<T extends Parcelable>
34        implements MarshalQueryable<T> {
35
36    private static final String TAG = "MarshalParcelable";
37    private static final boolean DEBUG = false;
38
39    private static final String FIELD_CREATOR = "CREATOR";
40
41    private class MarshalerParcelable extends Marshaler<T> {
42
43        private final Class<T> mClass;
44        private final Parcelable.Creator<T> mCreator;
45
46        @SuppressWarnings("unchecked")
47        protected MarshalerParcelable(TypeReference<T> typeReference,
48                int nativeType) {
49            super(MarshalQueryableParcelable.this, typeReference, nativeType);
50
51            mClass = (Class<T>)typeReference.getRawType();
52            Field creatorField;
53            try {
54                creatorField = mClass.getDeclaredField(FIELD_CREATOR);
55            } catch (NoSuchFieldException e) {
56                // Impossible. All Parcelable implementations must have a 'CREATOR' static field
57                throw new AssertionError(e);
58            }
59
60            try {
61                mCreator = (Parcelable.Creator<T>)creatorField.get(null);
62            } catch (IllegalAccessException e) {
63                // Impossible: All 'CREATOR' static fields must be public
64                throw new AssertionError(e);
65            } catch (IllegalArgumentException e) {
66                // Impossible: This is a static field, so null must be ok
67                throw new AssertionError(e);
68            }
69        }
70
71        @Override
72        public void marshal(T value, ByteBuffer buffer) {
73            if (DEBUG) {
74                Log.v(TAG, "marshal " + value);
75            }
76
77            Parcel parcel = Parcel.obtain();
78            byte[] parcelContents;
79
80            try {
81                value.writeToParcel(parcel, /*flags*/0);
82
83                if (parcel.hasFileDescriptors()) {
84                    throw new UnsupportedOperationException(
85                            "Parcelable " + value + " must not have file descriptors");
86                }
87
88                parcelContents = parcel.marshall();
89            }
90            finally {
91                parcel.recycle();
92            }
93
94            if (parcelContents.length == 0) {
95                throw new AssertionError("No data marshaled for " + value);
96            }
97
98            buffer.put(parcelContents);
99        }
100
101        @Override
102        public T unmarshal(ByteBuffer buffer) {
103            if (DEBUG) {
104                Log.v(TAG, "unmarshal, buffer remaining " + buffer.remaining());
105            }
106
107            /*
108             * Quadratically slow when marshaling an array of parcelables.
109             *
110             * Read out the entire byte buffer as an array, then copy it into the parcel.
111             *
112             * Once we unparcel the entire object, advance the byte buffer by only how many
113             * bytes the parcel actually used up.
114             *
115             * Future: If we ever do need to use parcelable arrays, we can do this a little smarter
116             * by reading out a chunk like 4,8,16,24 each time, but not sure how to detect
117             * parcels being too short in this case.
118             *
119             * Future: Alternatively use Parcel#obtain(long) directly into the native
120             * pointer of a ByteBuffer, which would not copy if the ByteBuffer was direct.
121             */
122            buffer.mark();
123
124            Parcel parcel = Parcel.obtain();
125            try {
126                int maxLength = buffer.remaining();
127
128                byte[] remaining = new byte[maxLength];
129                buffer.get(remaining);
130
131                parcel.unmarshall(remaining, /*offset*/0, maxLength);
132                parcel.setDataPosition(/*pos*/0);
133
134                T value = mCreator.createFromParcel(parcel);
135                int actualLength = parcel.dataPosition();
136
137                if (actualLength == 0) {
138                    throw new AssertionError("No data marshaled for " + value);
139                }
140
141                // set the position past the bytes the parcelable actually used
142                buffer.reset();
143                buffer.position(buffer.position() + actualLength);
144
145                if (DEBUG) {
146                    Log.v(TAG, "unmarshal, parcel length was " + actualLength);
147                    Log.v(TAG, "unmarshal, value is " + value);
148                }
149
150                return mClass.cast(value);
151            } finally {
152                parcel.recycle();
153            }
154        }
155
156        @Override
157        public int getNativeSize() {
158            return NATIVE_SIZE_DYNAMIC;
159        }
160
161        @Override
162        public int calculateMarshalSize(T value) {
163            Parcel parcel = Parcel.obtain();
164            try {
165                value.writeToParcel(parcel, /*flags*/0);
166                int length = parcel.marshall().length;
167
168                if (DEBUG) {
169                    Log.v(TAG, "calculateMarshalSize, length when parceling "
170                            + value + " is " + length);
171                }
172
173                return length;
174            } finally {
175                parcel.recycle();
176            }
177        }
178    }
179
180    @Override
181    public Marshaler<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
182        return new MarshalerParcelable(managedType, nativeType);
183    }
184
185    @Override
186    public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
187        return Parcelable.class.isAssignableFrom(managedType.getRawType());
188    }
189
190}
191