content: /*
   * 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
   *
   * <p>Use with extreme caution! File descriptors and binders will not be marshaled across.</p>
   */
  public class MarshalQueryableParcelable<T extends Parcelable>
          implements MarshalQueryable<T> {
  
      private static final String TAG = "MarshalParcelable";
      private static final boolean DEBUG = false;
  
      private static final String FIELD_CREATOR = "CREATOR";
  
      private class MarshalerParcelable extends Marshaler<T> {
  
          private final Class<T> mClass;
          private final Parcelable.Creator<T> mCreator;
  
          @SuppressWarnings("unchecked")
          protected MarshalerParcelable(TypeReference<T> typeReference,
                  int nativeType) {
              super(MarshalQueryableParcelable.this, typeReference, nativeType);
  
              mClass = (Class<T>)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<T>)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<T> createMarshaler(TypeReference<T> managedType, int nativeType) {
          return new MarshalerParcelable(managedType, nativeType);
      }
  
      @Override
      public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
          return Parcelable.class.isAssignableFrom(managedType.getRawType());
      }
  
  }
  