Camera2: Immutable metadata

Make all camera metadata immutable once created; requests are
created using CameraRequest.Builder.

- Separate CameraMetadata implementation from interface
- Implement deep copying of metadata
- Requests/results/properties have-a native implementation

Bug: 10360518
Change-Id: Ia6300c237219d39f70c63156fa9ca666d951a36e
diff --git a/api/current.txt b/api/current.txt
index 85fe16c..d69170f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10804,7 +10804,7 @@
     method public abstract void captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void close() throws java.lang.Exception;
     method public abstract void configureOutputs(java.util.List<android.view.Surface>) throws android.hardware.camera2.CameraAccessException;
-    method public abstract android.hardware.camera2.CaptureRequest createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
+    method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
     method public abstract void flush() throws android.hardware.camera2.CameraAccessException;
     method public abstract java.lang.String getId();
     method public abstract android.hardware.camera2.CameraProperties getProperties() throws android.hardware.camera2.CameraAccessException;
@@ -10850,14 +10850,8 @@
     method public void onCameraUnavailable(java.lang.String);
   }
 
-  public class CameraMetadata implements java.lang.AutoCloseable android.os.Parcelable {
-    ctor public CameraMetadata();
-    method public void close() throws java.lang.Exception;
-    method public int describeContents();
-    method public T get(android.hardware.camera2.CameraMetadata.Key<T>);
-    method public void readFromParcel(android.os.Parcel);
-    method public void set(android.hardware.camera2.CameraMetadata.Key<T>, T);
-    method public void writeToParcel(android.os.Parcel, int);
+  public abstract class CameraMetadata {
+    method public abstract T get(android.hardware.camera2.CameraMetadata.Key<T>);
     field public static final int COLOR_CORRECTION_MODE_FAST = 1; // 0x1
     field public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int COLOR_CORRECTION_MODE_TRANSFORM_MATRIX = 0; // 0x0
@@ -10941,7 +10935,6 @@
     field public static final int CONTROL_SCENE_MODE_SUNSET = 10; // 0xa
     field public static final int CONTROL_SCENE_MODE_THEATRE = 7; // 0x7
     field public static final int CONTROL_SCENE_MODE_UNSUPPORTED = 0; // 0x0
-    field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int EDGE_MODE_FAST = 1; // 0x1
     field public static final int EDGE_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int EDGE_MODE_OFF = 0; // 0x0
@@ -10974,14 +10967,13 @@
   }
 
   public static class CameraMetadata.Key {
-    ctor public CameraMetadata.Key(java.lang.String, java.lang.Class<T>);
     method public final boolean equals(java.lang.Object);
     method public final java.lang.String getName();
     method public final int hashCode();
   }
 
   public final class CameraProperties extends android.hardware.camera2.CameraMetadata {
-    ctor public CameraProperties();
+    method public T get(android.hardware.camera2.CameraMetadata.Key<T>);
     field public static final android.hardware.camera2.CameraMetadata.Key CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
     field public static final android.hardware.camera2.CameraMetadata.Key CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
     field public static final android.hardware.camera2.CameraMetadata.Key CONTROL_AE_COMPENSATION_RANGE;
@@ -11024,10 +11016,10 @@
   }
 
   public final class CaptureRequest extends android.hardware.camera2.CameraMetadata implements android.os.Parcelable {
-    method public void addTarget(android.view.Surface);
+    method public int describeContents();
+    method public T get(android.hardware.camera2.CameraMetadata.Key<T>);
     method public java.lang.Object getTag();
-    method public void removeTarget(android.view.Surface);
-    method public void setTag(java.lang.Object);
+    method public void writeToParcel(android.os.Parcel, int);
     field public static final android.hardware.camera2.CameraMetadata.Key BLACK_LEVEL_LOCK;
     field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_GAINS;
     field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_MODE;
@@ -11077,7 +11069,17 @@
     field public static final android.hardware.camera2.CameraMetadata.Key TONEMAP_MODE;
   }
 
+  public static final class CaptureRequest.Builder {
+    method public void addTarget(android.view.Surface);
+    method public android.hardware.camera2.CaptureRequest build();
+    method public T get(android.hardware.camera2.CameraMetadata.Key<T>);
+    method public void removeTarget(android.view.Surface);
+    method public void set(android.hardware.camera2.CameraMetadata.Key<T>, T);
+    method public void setTag(java.lang.Object);
+  }
+
   public final class CaptureResult extends android.hardware.camera2.CameraMetadata {
+    method public T get(android.hardware.camera2.CameraMetadata.Key<T>);
     field public static final android.hardware.camera2.CameraMetadata.Key BLACK_LEVEL_LOCK;
     field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_GAINS;
     field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_TRANSFORM;
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 33da80f..75c0f7d 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -230,15 +230,17 @@
     public void configureOutputs(List<Surface> outputs) throws CameraAccessException;
 
     /**
-     * <p>Create a {@link CaptureRequest} initialized with template for a target
-     * use case. The settings are chosen to be the best options for the specific
-     * camera device, so it is not recommended to reuse the same request for a
-     * different camera device; create a request for that device and override
-     * the settings as desired, instead.</p>
+     * <p>Create a {@link CaptureRequest.Builder} for new capture requests,
+     * initialized with template for a target use case. The settings are chosen
+     * to be the best options for the specific camera device, so it is not
+     * recommended to reuse the same request for a different camera device;
+     * create a builder specific for that device and template and override the
+     * settings as desired, instead.</p>
      *
      * @param templateType An enumeration selecting the use case for this
      * request; one of the CameraDevice.TEMPLATE_ values.
-     * @return a filled-in CaptureRequest, except for output streams
+     * @return a builder for a capture request, initialized with default
+     * settings for that template, and no output streams
      *
      * @throws IllegalArgumentException if the templateType is not in the list
      * of supported templates.
@@ -252,7 +254,7 @@
      * @see #TEMPLATE_VIDEO_SNAPSHOT
      * @see #TEMPLATE_MANUAL
      */
-    public CaptureRequest createCaptureRequest(int templateType)
+    public CaptureRequest.Builder createCaptureRequest(int templateType)
             throws CameraAccessException;
 
     /**
@@ -262,10 +264,10 @@
      * including sensor, lens, flash, and post-processing settings.</p>
      *
      * <p>Each request will produce one {@link CaptureResult} and produce new
-     * frames for one or more target Surfaces, set with CaptureRequests's {@link
-     * CaptureRequest#addTarget}. The target surfaces must be configured as
-     * active outputs with {@link #configureOutputs} before calling this
-     * method.</p>
+     * frames for one or more target Surfaces, set with the CaptureRequest
+     * builder's {@link CaptureRequest.Builder#addTarget} method. The target
+     * surfaces must be configured as active outputs with
+     * {@link #configureOutputs} before calling this method.</p>
      *
      * <p>Multiple requests can be in progress at once. They are processed in
      * first-in, first-out order, with minimal delays between each
@@ -303,10 +305,11 @@
      * calls.
      *
      * <p>The requests will be captured in order, each capture producing one
-     * {@link CaptureResult} and image buffers for one or more target {@link
-     * android.view.Surface surfaces}. The target surfaces for each request (set
-     * with {@link CaptureRequest#addTarget}) must be configured as active
-     * outputs with {@link #configureOutputs} before calling this method.</p>
+     * {@link CaptureResult} and image buffers for one or more target
+     * {@link android.view.Surface surfaces}. The target surfaces for each
+     * request (set with {@link CaptureRequest.Builder#addTarget}) must be
+     * configured as active outputs with {@link #configureOutputs} before
+     * calling this method.</p>
      *
      * <p>The main difference between this method and simply calling
      * {@link #capture} repeatedly is that this method guarantees that no
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index d294014..4ad9259 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -20,6 +20,7 @@
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
 import android.hardware.IProCameraUser;
+import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.hardware.camera2.utils.CameraRuntimeException;
 import android.hardware.camera2.utils.BinderHolder;
@@ -178,7 +179,7 @@
         // TODO: implement and call a service function to get the capabilities on C++ side
 
         // TODO: get properties from service
-        return new CameraProperties();
+        return new CameraProperties(new CameraMetadataNative());
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 70d777f..18fffc0 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -16,20 +16,7 @@
 
 package android.hardware.camera2;
 
-import android.hardware.camera2.impl.MetadataMarshalClass;
-import android.hardware.camera2.impl.MetadataMarshalRect;
-import android.hardware.camera2.impl.MetadataMarshalSize;
-import android.hardware.camera2.impl.MetadataMarshalString;
-import android.os.Parcelable;
-import android.os.Parcel;
-import android.util.Log;
-
-import java.lang.reflect.Array;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
+import android.hardware.camera2.impl.CameraMetadataNative;
 
 /**
  * The base class for camera controls and information.
@@ -42,62 +29,12 @@
  * @see CameraManager
  * @see CameraProperties
  **/
-public class CameraMetadata implements Parcelable, AutoCloseable {
-
-    public CameraMetadata() {
-        mMetadataMap = new HashMap<Key<?>, Object>();
-
-        mMetadataPtr = nativeAllocate();
-        if (mMetadataPtr == 0) {
-            throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
-        }
-    }
-
-    public static final Parcelable.Creator<CameraMetadata> CREATOR =
-            new Parcelable.Creator<CameraMetadata>() {
-        @Override
-        public CameraMetadata createFromParcel(Parcel in) {
-            CameraMetadata metadata = new CameraMetadata();
-            metadata.readFromParcel(in);
-            return metadata;
-        }
-
-        @Override
-        public CameraMetadata[] newArray(int size) {
-            return new CameraMetadata[size];
-        }
-    };
-
-    private static final String TAG = "CameraMetadataJV";
+public abstract class CameraMetadata {
 
     /**
-     * Set a camera metadata field to a value. The field definitions can be
-     * found in {@link CameraProperties}, {@link CaptureResult}, and
-     * {@link CaptureRequest}.
-     *
-     * @param key The metadata field to write.
-     * @param value The value to set the field to, which must be of a matching
-     * type to the key.
+     * @hide
      */
-    public <T> void set(Key<T> key, T value) {
-        int tag = key.getTag();
-
-        if (value == null) {
-            writeValues(tag, null);
-            return;
-        }
-
-        int nativeType = getNativeType(tag);
-
-        int size = packSingle(value, null, key.mType, nativeType, /* sizeOnly */true);
-
-        // TODO: Optimization. Cache the byte[] and reuse if the size is big enough.
-        byte[] values = new byte[size];
-
-        ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
-        packSingle(value, buffer, key.mType, nativeType, /*sizeOnly*/false);
-
-        writeValues(tag, values);
+    protected CameraMetadata() {
     }
 
     /**
@@ -110,342 +47,16 @@
      * @param key The metadata field to read.
      * @return The value of that key, or {@code null} if the field is not set.
      */
-    @SuppressWarnings("unchecked")
-    public <T> T get(Key<T> key) {
-        int tag = key.getTag();
-        byte[] values = readValues(tag);
-        if (values == null) {
-            return null;
-        }
-
-        int nativeType = getNativeType(tag);
-
-        ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
-        return unpackSingle(buffer, key.mType, nativeType);
-    }
-
-    // Keep up-to-date with camera_metadata.h
-    /**
-     * @hide
-     */
-    public static final int TYPE_BYTE = 0;
-    /**
-     * @hide
-     */
-    public static final int TYPE_INT32 = 1;
-    /**
-     * @hide
-     */
-    public static final int TYPE_FLOAT = 2;
-    /**
-     * @hide
-     */
-    public static final int TYPE_INT64 = 3;
-    /**
-     * @hide
-     */
-    public static final int TYPE_DOUBLE = 4;
-    /**
-     * @hide
-     */
-    public static final int TYPE_RATIONAL = 5;
-    /**
-     * @hide
-     */
-    public static final int NUM_TYPES = 6;
-
-    private static int getTypeSize(int nativeType) {
-        switch(nativeType) {
-            case TYPE_BYTE:
-                return 1;
-            case TYPE_INT32:
-            case TYPE_FLOAT:
-                return 4;
-            case TYPE_INT64:
-            case TYPE_DOUBLE:
-            case TYPE_RATIONAL:
-                return 8;
-        }
-
-        throw new UnsupportedOperationException("Unknown type, can't get size "
-                + nativeType);
-    }
-
-    private static Class<?> getExpectedType(int nativeType) {
-        switch(nativeType) {
-            case TYPE_BYTE:
-                return Byte.TYPE;
-            case TYPE_INT32:
-                return Integer.TYPE;
-            case TYPE_FLOAT:
-                return Float.TYPE;
-            case TYPE_INT64:
-                return Long.TYPE;
-            case TYPE_DOUBLE:
-                return Double.TYPE;
-            case TYPE_RATIONAL:
-                return Rational.class;
-        }
-
-        throw new UnsupportedOperationException("Unknown type, can't map to Java type "
-                + nativeType);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> int packSingleNative(T value, ByteBuffer buffer, Class<T> type,
-            int nativeType, boolean sizeOnly) {
-
-        if (!sizeOnly) {
-            /**
-             * Rewrite types when the native type doesn't match the managed type
-             *  - Boolean -> Byte
-             *  - Integer -> Byte
-             */
-
-            if (nativeType == TYPE_BYTE && type == Boolean.TYPE) {
-                // Since a boolean can't be cast to byte, and we don't want to use putBoolean
-                boolean asBool = (Boolean) value;
-                byte asByte = (byte) (asBool ? 1 : 0);
-                value = (T) (Byte) asByte;
-            } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) {
-                int asInt = (Integer) value;
-                byte asByte = (byte) asInt;
-                value = (T) (Byte) asByte;
-            } else if (type != getExpectedType(nativeType)) {
-                throw new UnsupportedOperationException("Tried to pack a type of " + type +
-                        " but we expected the type to be " + getExpectedType(nativeType));
-            }
-
-            if (nativeType == TYPE_BYTE) {
-                buffer.put((Byte) value);
-            } else if (nativeType == TYPE_INT32) {
-                buffer.putInt((Integer) value);
-            } else if (nativeType == TYPE_FLOAT) {
-                buffer.putFloat((Float) value);
-            } else if (nativeType == TYPE_INT64) {
-                buffer.putLong((Long) value);
-            } else if (nativeType == TYPE_DOUBLE) {
-                buffer.putDouble((Double) value);
-            } else if (nativeType == TYPE_RATIONAL) {
-                Rational r = (Rational) value;
-                buffer.putInt(r.getNumerator());
-                buffer.putInt(r.getDenominator());
-            }
-
-        }
-
-        return getTypeSize(nativeType);
-    }
-
-    @SuppressWarnings({"unchecked", "rawtypes"})
-    private static <T> int packSingle(T value, ByteBuffer buffer, Class<T> type, int nativeType,
-            boolean sizeOnly) {
-
-        int size = 0;
-
-        if (type.isPrimitive() || type == Rational.class) {
-            size = packSingleNative(value, buffer, type, nativeType, sizeOnly);
-        } else if (type.isEnum()) {
-            size = packEnum((Enum)value, buffer, (Class<Enum>)type, nativeType, sizeOnly);
-        } else if (type.isArray()) {
-            size = packArray(value, buffer, type, nativeType, sizeOnly);
-        } else {
-            size = packClass(value, buffer, type, nativeType, sizeOnly);
-        }
-
-        return size;
-    }
-
-    private static <T extends Enum<T>> int packEnum(T value, ByteBuffer buffer, Class<T> type,
-            int nativeType, boolean sizeOnly) {
-
-        // TODO: add support for enums with their own values.
-        return packSingleNative(getEnumValue(value), buffer, Integer.TYPE, nativeType, sizeOnly);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> int packClass(T value, ByteBuffer buffer, Class<T> type, int nativeType,
-            boolean sizeOnly) {
-
-        MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType);
-        if (marshaler == null) {
-            throw new IllegalArgumentException(String.format("Unknown Key type: %s", type));
-        }
-
-        return marshaler.marshal(value, buffer, nativeType, sizeOnly);
-    }
-
-    private static <T> int packArray(T value, ByteBuffer buffer, Class<T> type, int nativeType,
-            boolean sizeOnly) {
-
-        int size = 0;
-        int arrayLength = Array.getLength(value);
-
-        @SuppressWarnings("unchecked")
-        Class<Object> componentType = (Class<Object>)type.getComponentType();
-
-        for (int i = 0; i < arrayLength; ++i) {
-            size += packSingle(Array.get(value, i), buffer, componentType, nativeType, sizeOnly);
-        }
-
-        return size;
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T unpackSingleNative(ByteBuffer buffer, Class<T> type, int nativeType) {
-
-        T val;
-
-        if (nativeType == TYPE_BYTE) {
-            val = (T) (Byte) buffer.get();
-        } else if (nativeType == TYPE_INT32) {
-            val = (T) (Integer) buffer.getInt();
-        } else if (nativeType == TYPE_FLOAT) {
-            val = (T) (Float) buffer.getFloat();
-        } else if (nativeType == TYPE_INT64) {
-            val = (T) (Long) buffer.getLong();
-        } else if (nativeType == TYPE_DOUBLE) {
-            val = (T) (Double) buffer.getDouble();
-        } else if (nativeType == TYPE_RATIONAL) {
-            val = (T) new Rational(buffer.getInt(), buffer.getInt());
-        } else {
-            throw new UnsupportedOperationException("Unknown type, can't unpack a native type "
-                + nativeType);
-        }
-
-        /**
-         * Rewrite types when the native type doesn't match the managed type
-         *  - Byte -> Boolean
-         *  - Byte -> Integer
-         */
-
-        if (nativeType == TYPE_BYTE && type == Boolean.TYPE) {
-            // Since a boolean can't be cast to byte, and we don't want to use getBoolean
-            byte asByte = (Byte) val;
-            boolean asBool = asByte != 0;
-            val = (T) (Boolean) asBool;
-        } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) {
-            byte asByte = (Byte) val;
-            int asInt = asByte;
-            val = (T) (Integer) asInt;
-        } else if (type != getExpectedType(nativeType)) {
-            throw new UnsupportedOperationException("Tried to unpack a type of " + type +
-                    " but we expected the type to be " + getExpectedType(nativeType));
-        }
-
-        return val;
-    }
-
-    @SuppressWarnings({"unchecked", "rawtypes"})
-    private static <T> T unpackSingle(ByteBuffer buffer, Class<T> type, int nativeType) {
-
-        if (type.isPrimitive() || type == Rational.class) {
-            return unpackSingleNative(buffer, type, nativeType);
-        }
-
-        if (type.isEnum()) {
-            return (T) unpackEnum(buffer, (Class<Enum>)type, nativeType);
-        }
-
-        if (type.isArray()) {
-            return unpackArray(buffer, type, nativeType);
-        }
-
-        T instance = unpackClass(buffer, type, nativeType);
-
-        return instance;
-    }
-
-    private static <T extends Enum<T>> T unpackEnum(ByteBuffer buffer, Class<T> type,
-            int nativeType) {
-        int ordinal = unpackSingleNative(buffer, Integer.TYPE, nativeType);
-        return getEnumFromValue(type, ordinal);
-    }
-
-    private static <T> T unpackClass(ByteBuffer buffer, Class<T> type, int nativeType) {
-
-        MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType);
-        if (marshaler == null) {
-            throw new IllegalArgumentException("Unknown class type: " + type);
-        }
-
-        return marshaler.unmarshal(buffer, nativeType);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T unpackArray(ByteBuffer buffer, Class<T> type, int nativeType) {
-
-        Class<?> componentType = type.getComponentType();
-        Object array;
-
-        int elementSize = getTypeSize(nativeType);
-
-        MetadataMarshalClass<?> marshaler = getMarshaler(componentType, nativeType);
-        if (marshaler != null) {
-            elementSize = marshaler.getNativeSize(nativeType);
-        }
-
-        if (elementSize != MetadataMarshalClass.NATIVE_SIZE_DYNAMIC) {
-            int remaining = buffer.remaining();
-            int arraySize = remaining / elementSize;
-
-            Log.v(TAG,
-                    String.format(
-                            "Attempting to unpack array (count = %d, element size = %d, bytes " +
-                                    "remaining = %d) for type %s",
-                            arraySize, elementSize, remaining, type));
-
-            array = Array.newInstance(componentType, arraySize);
-            for (int i = 0; i < arraySize; ++i) {
-               Object elem = unpackSingle(buffer, componentType, nativeType);
-               Array.set(array, i, elem);
-            }
-        } else {
-            // Dynamic size, use an array list.
-            ArrayList<Object> arrayList = new ArrayList<Object>();
-
-            int primitiveSize = getTypeSize(nativeType);
-            while (buffer.remaining() >= primitiveSize) {
-                Object elem = unpackSingle(buffer, componentType, nativeType);
-                arrayList.add(elem);
-            }
-
-            array = arrayList.toArray((T[]) Array.newInstance(componentType, 0));
-        }
-
-        if (buffer.remaining() != 0) {
-            Log.e(TAG, "Trailing bytes (" + buffer.remaining() + ") left over after unpacking "
-                    + type);
-        }
-
-        return (T) array;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        nativeWriteToParcel(dest);
-    }
-
-    /**
-     * Expand this object from a Parcel.
-     * @param in The Parcel from which the object should be read
-     */
-    public void readFromParcel(Parcel in) {
-        nativeReadFromParcel(in);
-    }
+    public abstract <T> T get(Key<T> key);
 
     public static class Key<T> {
 
         private boolean mHasTag;
         private int mTag;
         private final Class<T> mType;
+        private final String mName;
 
-        /*
+        /**
          * @hide
          */
         public Key(String name, Class<T> type) {
@@ -483,8 +94,6 @@
             return mName.equals(lhs.mName);
         }
 
-        private final String mName;
-
         /**
          * <p>
          * Get the tag corresponding to this key. This enables insertion into the
@@ -499,267 +108,20 @@
          */
         public final int getTag() {
             if (!mHasTag) {
-                mTag = CameraMetadata.getTag(mName);
+                mTag = CameraMetadataNative.getTag(mName);
                 mHasTag = true;
             }
             return mTag;
         }
-    }
 
-    private final Map<Key<?>, Object> mMetadataMap;
-
-    private long mMetadataPtr; // native CameraMetadata*
-
-    private native long nativeAllocate();
-    private native synchronized void nativeWriteToParcel(Parcel dest);
-    private native synchronized void nativeReadFromParcel(Parcel source);
-    private native synchronized void nativeSwap(CameraMetadata other) throws NullPointerException;
-    private native synchronized void nativeClose();
-    private native synchronized boolean nativeIsEmpty();
-    private native synchronized int nativeGetEntryCount();
-
-    private native synchronized byte[] nativeReadValues(int tag);
-    private native synchronized void nativeWriteValues(int tag, byte[] src);
-
-    private static native int nativeGetTagFromKey(String keyName)
-            throws IllegalArgumentException;
-    private static native int nativeGetTypeFromTag(int tag)
-            throws IllegalArgumentException;
-    private static native void nativeClassInit();
-
-    /**
-     * <p>Perform a 0-copy swap of the internal metadata with another object.</p>
-     *
-     * <p>Useful to convert a CameraMetadata into e.g. a CaptureRequest.</p>
-     *
-     * @param other Metadata to swap with
-     * @throws NullPointerException if other was null
-     * @hide
-     */
-    public void swap(CameraMetadata other) {
-        nativeSwap(other);
-    }
-
-    /**
-     * @hide
-     */
-    public int getEntryCount() {
-        return nativeGetEntryCount();
-    }
-
-    /**
-     * Does this metadata contain at least 1 entry?
-     *
-     * @hide
-     */
-    public boolean isEmpty() {
-        return nativeIsEmpty();
-    }
-
-    /**
-     * <p>Closes this object, and releases all native resources associated with it.</p>
-     *
-     * <p>Calling any other public method after this will result in an IllegalStateException
-     * being thrown.</p>
-     */
-    @Override
-    public void close() throws Exception {
-        // this sets mMetadataPtr to 0
-        nativeClose();
-        mMetadataPtr = 0; // set it to 0 again to prevent eclipse from making this field final
-    }
-
-    /**
-     * Whether or not {@link #close} has already been called (at least once) on this object.
-     * @hide
-     */
-    public boolean isClosed() {
-        synchronized (this) {
-            return mMetadataPtr == 0;
+        /**
+         * @hide
+         */
+        public final Class<T> getType() {
+            return mType;
         }
     }
 
-    /**
-     * Convert a key string into the equivalent native tag.
-     *
-     * @throws IllegalArgumentException if the key was not recognized
-     * @throws NullPointerException if the key was null
-     *
-     * @hide
-     */
-    public static int getTag(String key) {
-        return nativeGetTagFromKey(key);
-    }
-
-    /**
-     * Get the underlying native type for a tag.
-     *
-     * @param tag An integer tag, see e.g. {@link #getTag}
-     * @return An int enum for the metadata type, see e.g. {@link #TYPE_BYTE}
-     *
-     * @hide
-     */
-    public static int getNativeType(int tag) {
-        return nativeGetTypeFromTag(tag);
-    }
-
-    /**
-     * <p>Updates the existing entry for tag with the new bytes pointed by src, erasing
-     * the entry if src was null.</p>
-     *
-     * <p>An empty array can be passed in to update the entry to 0 elements.</p>
-     *
-     * @param tag An integer tag, see e.g. {@link #getTag}
-     * @param src An array of bytes, or null to erase the entry
-     *
-     * @hide
-     */
-    public void writeValues(int tag, byte[] src) {
-        nativeWriteValues(tag, src);
-    }
-
-    /**
-     * <p>Returns a byte[] of data corresponding to this tag. Use a wrapped bytebuffer to unserialize
-     * the data properly.</p>
-     *
-     * <p>An empty array can be returned to denote an existing entry with 0 elements.</p>
-     *
-     * @param tag An integer tag, see e.g. {@link #getTag}
-     *
-     * @return {@code null} if there were 0 entries for this tag, a byte[] otherwise.
-     * @hide
-     */
-    public byte[] readValues(int tag) {
-     // TODO: Optimization. Native code returns a ByteBuffer instead.
-        return nativeReadValues(tag);
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            close();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private static final HashMap<Class<? extends Enum>, int[]> sEnumValues =
-            new HashMap<Class<? extends Enum>, int[]>();
-    /**
-     * Register a non-sequential set of values to be used with the pack/unpack functions.
-     * This enables get/set to correctly marshal the enum into a value that is C-compatible.
-     *
-     * @param enumType The class for an enum
-     * @param values A list of values mapping to the ordinals of the enum
-     *
-     * @hide
-     */
-    public static <T extends Enum<T>> void registerEnumValues(Class<T> enumType, int[] values) {
-        if (enumType.getEnumConstants().length != values.length) {
-            throw new IllegalArgumentException(
-                    "Expected values array to be the same size as the enumTypes values "
-                            + values.length + " for type " + enumType);
-        }
-
-        Log.v(TAG, "Registered enum values for type " + enumType + " values");
-
-        sEnumValues.put(enumType, values);
-    }
-
-    /**
-     * Get the numeric value from an enum. This is usually the same as the ordinal value for
-     * enums that have fully sequential values, although for C-style enums the range of values
-     * may not map 1:1.
-     *
-     * @param enumValue Enum instance
-     * @return Int guaranteed to be ABI-compatible with the C enum equivalent
-     */
-    private static <T extends Enum<T>> int getEnumValue(T enumValue) {
-        int[] values;
-        values = sEnumValues.get(enumValue.getClass());
-
-        int ordinal = enumValue.ordinal();
-        if (values != null) {
-            return values[ordinal];
-        }
-
-        return ordinal;
-    }
-
-    /**
-     * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method.
-     *
-     * @param enumType Class of the enum we want to find
-     * @param value The numeric value of the enum
-     * @return An instance of the enum
-     */
-    private static <T extends Enum<T>> T getEnumFromValue(Class<T> enumType, int value) {
-        int ordinal;
-
-        int[] registeredValues = sEnumValues.get(enumType);
-        if (registeredValues != null) {
-            ordinal = -1;
-
-            for (int i = 0; i < registeredValues.length; ++i) {
-                if (registeredValues[i] == value) {
-                    ordinal = i;
-                    break;
-                }
-            }
-        } else {
-            ordinal = value;
-        }
-
-        T[] values = enumType.getEnumConstants();
-
-        if (ordinal < 0 || ordinal >= values.length) {
-            throw new IllegalArgumentException(
-                    String.format(
-                            "Argument 'value' (%d) was not a valid enum value for type %s "
-                                    + "(registered? %b)",
-                            value,
-                            enumType, (registeredValues != null)));
-        }
-
-        return values[ordinal];
-    }
-
-    static HashMap<Class<?>, MetadataMarshalClass<?>> sMarshalerMap = new
-            HashMap<Class<?>, MetadataMarshalClass<?>>();
-
-    private static <T> void registerMarshaler(MetadataMarshalClass<T> marshaler) {
-        sMarshalerMap.put(marshaler.getMarshalingClass(), marshaler);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> MetadataMarshalClass<T> getMarshaler(Class<T> type, int nativeType) {
-        MetadataMarshalClass<T> marshaler = (MetadataMarshalClass<T>) sMarshalerMap.get(type);
-
-        if (marshaler != null && !marshaler.isNativeTypeSupported(nativeType)) {
-            throw new UnsupportedOperationException("Unsupported type " + nativeType +
-                    " to be marshalled to/from a " + type);
-        }
-
-        return marshaler;
-    }
-
-    /**
-     * We use a class initializer to allow the native code to cache some field offsets
-     */
-    static {
-        System.loadLibrary("media_jni");
-        nativeClassInit();
-
-        Log.v(TAG, "Shall register metadata marshalers");
-
-        // load built-in marshallers
-        registerMarshaler(new MetadataMarshalRect());
-        registerMarshaler(new MetadataMarshalSize());
-        registerMarshaler(new MetadataMarshalString());
-
-        Log.v(TAG, "Registered metadata marshalers");
-    }
-
     /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * The enum values below this point are generated from metadata
      * definitions in /system/media/camera/docs. Do not modify by hand or
diff --git a/core/java/android/hardware/camera2/CameraProperties.java b/core/java/android/hardware/camera2/CameraProperties.java
index ebbdd88..45c009f 100644
--- a/core/java/android/hardware/camera2/CameraProperties.java
+++ b/core/java/android/hardware/camera2/CameraProperties.java
@@ -16,6 +16,8 @@
 
 package android.hardware.camera2;
 
+import android.hardware.camera2.impl.CameraMetadataNative;
+
 /**
  * <p>The properties describing a
  * {@link CameraDevice CameraDevice}.</p>
@@ -29,6 +31,21 @@
  */
 public final class CameraProperties extends CameraMetadata {
 
+    private final CameraMetadataNative mProperties;
+
+    /**
+     * Takes ownership of the passed-in properties object
+     * @hide
+     */
+    public CameraProperties(CameraMetadataNative properties) {
+        mProperties = properties;
+    }
+
+    @Override
+    public <T> T get(Key<T> key) {
+        return mProperties.get(key);
+    }
+
     /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * The key entries below this point are generated from metadata
      * definitions in /system/media/camera/docs. Do not modify by hand or
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index e7c1b54..c3a636d 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -16,6 +16,7 @@
 
 package android.hardware.camera2;
 
+import android.hardware.camera2.impl.CameraMetadataNative;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.Surface;
@@ -24,13 +25,16 @@
 
 
 /**
- * <p>All the settings required to capture a single image from the image sensor.</p>
+ * <p>An immutable package of settings and outputs needed to capture a single
+ * image from the camera device.</p>
  *
  * <p>Contains the configuration for the capture hardware (sensor, lens, flash),
- * the processing pipeline, the control algorithms, and the output buffers.</p>
+ * the processing pipeline, the control algorithms, and the output buffers. Also
+ * contains the list of target Surfaces to send image data to for this
+ * capture.</p>
  *
- * <p>CaptureRequests can be created by calling
- * {@link CameraDevice#createCaptureRequest}</p>
+ * <p>CaptureRequests can be created by using a {@link Builder} instance,
+ * obtained by calling {@link CameraDevice#createCaptureRequest}</p>
  *
  * <p>CaptureRequests are given to {@link CameraDevice#capture} or
  * {@link CameraDevice#setRepeatingRequest} to capture images from a camera.</p>
@@ -38,7 +42,8 @@
  * <p>Each request can specify a different subset of target Surfaces for the
  * camera to send the captured data to. All the surfaces used in a request must
  * be part of the surface list given to the last call to
- * {@link CameraDevice#configureOutputs}.</p>
+ * {@link CameraDevice#configureOutputs}, when the request is submitted to the
+ * camera device.</p>
  *
  * <p>For example, a request meant for repeating preview might only include the
  * Surface for the preview SurfaceView or SurfaceTexture, while a
@@ -47,64 +52,43 @@
  *
  * @see CameraDevice#capture
  * @see CameraDevice#setRepeatingRequest
- * @see CameraDevice#createRequest
+ * @see CameraDevice#createCaptureRequest
  */
 public final class CaptureRequest extends CameraMetadata implements Parcelable {
 
-    private final Object mLock = new Object();
-    private final HashSet<Surface> mSurfaceSet = new HashSet<Surface>();
+    private final HashSet<Surface> mSurfaceSet;
+    private final CameraMetadataNative mSettings;
+
     private Object mUserTag;
 
     /**
+     * Construct empty request
      * @hide
      */
     public CaptureRequest() {
+        mSettings = new CameraMetadataNative();
+        mSurfaceSet = new HashSet<Surface>();
     }
 
     /**
-     * <p>Add a surface to the list of targets for this request</p>
-     *
-     * <p>The Surface added must be one of the surfaces included in the last
-     * call to {@link CameraDevice#configureOutputs}.</p>
-     *
-     * <p>Adding a target more than once has no effect.</p>
-     *
-     * @param outputTarget Surface to use as an output target for this request
+     * Clone from source capture request
      */
-    public void addTarget(Surface outputTarget) {
-        synchronized (mLock) {
-            mSurfaceSet.add(outputTarget);
-        }
+    private CaptureRequest(CaptureRequest source) {
+        mSettings = new CameraMetadataNative(source.mSettings);
+        mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
     }
 
     /**
-     * <p>Remove a surface from the list of targets for this request.</p>
-     *
-     * <p>Removing a target that is not currently added has no effect.</p>
-     *
-     * @param outputTarget Surface to use as an output target for this request
+     * Take ownership of passed-in settings
      */
-    public void removeTarget(Surface outputTarget) {
-        synchronized (mLock) {
-            mSurfaceSet.remove(outputTarget);
-        }
+    private CaptureRequest(CameraMetadataNative settings) {
+        mSettings = settings;
+        mSurfaceSet = new HashSet<Surface>();
     }
 
-    /**
-     * Set a tag for this request.
-     *
-     * <p>This tag is not used for anything by the camera device, but can be
-     * used by an application to easily identify a CaptureRequest when it is
-     * returned by
-     * {@link CameraDevice.CaptureListener#onCaptureCompleted CaptureListener.onCaptureCompleted}
-     *
-     * @param tag an arbitrary Object to store with this request
-     * @see #getTag
-     */
-    public void setTag(Object tag) {
-        synchronized (mLock) {
-            mUserTag = tag;
-        }
+    @Override
+    public <T> T get(Key<T> key) {
+        return mSettings.get(key);
     }
 
     /**
@@ -118,12 +102,10 @@
      *
      * @return the last tag Object set on this request, or {@code null} if
      *     no tag has been set.
-     * @see #setTag
+     * @see Builder#setTag
      */
     public Object getTag() {
-        synchronized (mLock) {
-            return mUserTag;
-        }
+        return mUserTag;
     }
 
     public static final Parcelable.Creator<CaptureRequest> CREATOR =
@@ -132,6 +114,7 @@
         public CaptureRequest createFromParcel(Parcel in) {
             CaptureRequest request = new CaptureRequest();
             request.readFromParcel(in);
+
             return request;
         }
 
@@ -143,35 +126,152 @@
 
     /**
      * Expand this object from a Parcel.
+     * Hidden since this breaks the immutability of CaptureRequest, but is
+     * needed to receive CaptureRequests with aidl.
+     *
      * @param in The parcel from which the object should be read
+     * @hide
      */
-    @Override
     public void readFromParcel(Parcel in) {
-        synchronized (mLock) {
-            super.readFromParcel(in);
+        mSettings.readFromParcel(in);
 
-            mSurfaceSet.clear();
+        mSurfaceSet.clear();
 
-            Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
+        Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
 
-            if (parcelableArray == null) {
-                return;
-            }
+        if (parcelableArray == null) {
+            return;
+        }
 
-            for (Parcelable p : parcelableArray) {
-                Surface s = (Surface) p;
-                mSurfaceSet.add(s);
-            }
+        for (Parcelable p : parcelableArray) {
+            Surface s = (Surface) p;
+            mSurfaceSet.add(s);
         }
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        synchronized (mLock) {
-            super.writeToParcel(dest, flags);
+    public int describeContents() {
+        return 0;
+    }
 
-            dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags);
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mSettings.writeToParcel(dest, flags);
+        dest.writeParcelableArray(mSurfaceSet.toArray(new Surface[mSurfaceSet.size()]), flags);
+    }
+
+    /**
+     * A builder for capture requests.
+     *
+     * <p>To obtain a builder instance, use the
+     * {@link CameraDevice#createCaptureRequest} method, which initializes the
+     * request fields to one of the templates defined in {@link CameraDevice}.
+     *
+     * @see CameraDevice#createCaptureRequest
+     * @see #TEMPLATE_PREVIEW
+     * @see #TEMPLATE_RECORD
+     * @see #TEMPLATE_STILL_CAPTURE
+     * @see #TEMPLATE_VIDEO_SNAPSHOT
+     * @see #TEMPLATE_MANUAL
+     */
+    public final static class Builder {
+
+        private CaptureRequest mRequest;
+
+        /**
+         * Initialize the builder using the template; the request takes
+         * ownership of the template.
+         *
+         * @hide
+         */
+        public Builder(CameraMetadataNative template) {
+            mRequest = new CaptureRequest(template);
         }
+
+        /**
+         * <p>Add a surface to the list of targets for this request</p>
+         *
+         * <p>The Surface added must be one of the surfaces included in the most
+         * recent call to {@link CameraDevice#configureOutputs}, when the
+         * request is given to the camera device.</p>
+         *
+         * <p>Adding a target more than once has no effect.</p>
+         *
+         * @param outputTarget Surface to use as an output target for this request
+         */
+        public void addTarget(Surface outputTarget) {
+            mRequest.mSurfaceSet.add(outputTarget);
+        }
+
+        /**
+         * <p>Remove a surface from the list of targets for this request.</p>
+         *
+         * <p>Removing a target that is not currently added has no effect.</p>
+         *
+         * @param outputTarget Surface to use as an output target for this request
+         */
+        public void removeTarget(Surface outputTarget) {
+            mRequest.mSurfaceSet.remove(outputTarget);
+        }
+
+        /**
+         * Set a capture request field to a value. The field definitions can be
+         * found in {@link CaptureRequest}.
+         *
+         * @param key The metadata field to write.
+         * @param value The value to set the field to, which must be of a matching
+         * type to the key.
+         */
+        public <T> void set(Key<T> key, T value) {
+            mRequest.mSettings.set(key, value);
+        }
+
+        /**
+         * Get a capture request field value. The field definitions can be
+         * found in {@link CaptureRequest}.
+         *
+         * @throws IllegalArgumentException if the key was not valid
+         *
+         * @param key The metadata field to read.
+         * @return The value of that key, or {@code null} if the field is not set.
+         */
+        public <T> T get(Key<T> key) {
+            return mRequest.mSettings.get(key);
+        }
+
+        /**
+         * Set a tag for this request.
+         *
+         * <p>This tag is not used for anything by the camera device, but can be
+         * used by an application to easily identify a CaptureRequest when it is
+         * returned by
+         * {@link CameraDevice.CaptureListener#onCaptureCompleted CaptureListener.onCaptureCompleted}
+         *
+         * @param tag an arbitrary Object to store with this request
+         * @see CaptureRequest#getTag
+         */
+        public void setTag(Object tag) {
+            mRequest.mUserTag = tag;
+        }
+
+        /**
+         * Build a request using the current target Surfaces and settings.
+         *
+         * @return A new capture request instance, ready for submission to the
+         * camera device.
+         */
+        public CaptureRequest build() {
+            return new CaptureRequest(mRequest);
+        }
+
+
+        /**
+         * @hide
+         */
+        public boolean isEmpty() {
+            return mRequest.mSettings.isEmpty();
+        }
+
     }
 
     /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index bd151a2..f83dad7 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.camera2.impl.CameraMetadataNative;
 
 /**
  * <p>The results of a single image capture from the image sensor.</p>
@@ -34,10 +35,20 @@
  *
  */
 public final class CaptureResult extends CameraMetadata {
+
+    private final CameraMetadataNative mResults;
+
     /**
+     * Takes ownership of the passed-in properties object
      * @hide
      */
-    public CaptureResult() {
+    public CaptureResult(CameraMetadataNative results) {
+        mResults = results;
+    }
+
+    @Override
+    public <T> T get(Key<T> key) {
+        return mResults.get(key);
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 4172238..4054a92 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -16,7 +16,7 @@
 
 package android.hardware.camera2;
 
-import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.impl.CameraMetadataNative;
 
 /** @hide */
 interface ICameraDeviceCallbacks
@@ -26,5 +26,5 @@
      */
 
     oneway void notifyCallback(int msgType, int ext1, int ext2);
-    oneway void onResultReceived(int frameId, in CameraMetadata result);
+    oneway void onResultReceived(int frameId, in CameraMetadataNative result);
 }
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index b1724de..1936963 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -17,7 +17,7 @@
 package android.hardware.camera2;
 
 import android.view.Surface;
-import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.CaptureRequest;
 
 /** @hide */
@@ -40,9 +40,9 @@
     // non-negative value is the stream ID. negative value is status_t
     int createStream(int width, int height, int format, in Surface surface);
 
-    int createDefaultRequest(int templateId, out CameraMetadata request);
+    int createDefaultRequest(int templateId, out CameraMetadataNative request);
 
-    int getCameraInfo(out CameraMetadata info);
+    int getCameraInfo(out CameraMetadataNative info);
 
     int waitUntilIdle();
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 966fcfa..995555a 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -42,7 +42,7 @@
 import java.util.Stack;
 
 /**
- * HAL2.1+ implementation of CameraDevice Use CameraManager#open to instantiate
+ * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
  */
 public class CameraDevice implements android.hardware.camera2.CameraDevice {
 
@@ -90,8 +90,7 @@
     @Override
     public CameraProperties getProperties() throws CameraAccessException {
 
-        CameraProperties properties = new CameraProperties();
-        CameraMetadata info = new CameraMetadata();
+        CameraMetadataNative info = new CameraMetadataNative();
 
         try {
             mRemoteDevice.getCameraInfo(/*out*/info);
@@ -102,7 +101,7 @@
             return null;
         }
 
-        properties.swap(info);
+        CameraProperties properties = new CameraProperties(info);
         return properties;
     }
 
@@ -157,11 +156,11 @@
     }
 
     @Override
-    public CaptureRequest createCaptureRequest(int templateType) throws CameraAccessException {
-
+    public CaptureRequest.Builder createCaptureRequest(int templateType)
+            throws CameraAccessException {
         synchronized (mLock) {
 
-            CameraMetadata templatedRequest = new CameraMetadata();
+            CameraMetadataNative templatedRequest = new CameraMetadataNative();
 
             try {
                 mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest);
@@ -172,11 +171,10 @@
                 return null;
             }
 
-            CaptureRequest request = new CaptureRequest();
-            request.swap(templatedRequest);
+            CaptureRequest.Builder builder =
+                    new CaptureRequest.Builder(templatedRequest);
 
-            return request;
-
+            return builder;
         }
     }
 
@@ -404,7 +402,8 @@
         }
 
         @Override
-        public void onResultReceived(int requestId, CameraMetadata result) throws RemoteException {
+        public void onResultReceived(int requestId, CameraMetadataNative result)
+                throws RemoteException {
             if (DEBUG) {
                 Log.d(TAG, "Received result for id " + requestId);
             }
@@ -432,8 +431,7 @@
                 return;
             }
 
-            final CaptureResult resultAsCapture = new CaptureResult();
-            resultAsCapture.swap(result);
+            final CaptureResult resultAsCapture = new CaptureResult(result);
 
             holder.getHandler().post(
                 new Runnable() {
diff --git a/core/java/android/hardware/camera2/CameraMetadata.aidl b/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl
similarity index 89%
rename from core/java/android/hardware/camera2/CameraMetadata.aidl
rename to core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl
index 71dd471..4a89129 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.aidl
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.camera2;
+package android.hardware.camera2.impl;
 
 /** @hide */
-parcelable CameraMetadata;
+parcelable CameraMetadataNative;
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
new file mode 100644
index 0000000..020d7b6
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2013 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.impl;
+
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.Rational;
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of camera metadata marshal/unmarshal across Binder to
+ * the camera service
+ */
+public class CameraMetadataNative extends CameraMetadata implements Parcelable {
+
+    private static final String TAG = "CameraMetadataJV";
+
+    public CameraMetadataNative() {
+        super();
+        mMetadataPtr = nativeAllocate();
+        if (mMetadataPtr == 0) {
+            throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
+        }
+    }
+
+    /**
+     * Copy constructor - clone metadata
+     */
+    public CameraMetadataNative(CameraMetadataNative other) {
+        super();
+        mMetadataPtr = nativeAllocateCopy(other);
+        if (mMetadataPtr == 0) {
+            throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
+        }
+    }
+
+    public static final Parcelable.Creator<CameraMetadataNative> CREATOR =
+            new Parcelable.Creator<CameraMetadataNative>() {
+        @Override
+        public CameraMetadataNative createFromParcel(Parcel in) {
+            CameraMetadataNative metadata = new CameraMetadataNative();
+            metadata.readFromParcel(in);
+            return metadata;
+        }
+
+        @Override
+        public CameraMetadataNative[] newArray(int size) {
+            return new CameraMetadataNative[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        nativeWriteToParcel(dest);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T get(Key<T> key) {
+        int tag = key.getTag();
+        byte[] values = readValues(tag);
+        if (values == null) {
+            return null;
+        }
+
+        int nativeType = getNativeType(tag);
+
+        ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
+        return unpackSingle(buffer, key.getType(), nativeType);
+    }
+
+    public void readFromParcel(Parcel in) {
+        nativeReadFromParcel(in);
+    }
+
+    /**
+     * Set a camera metadata field to a value. The field definitions can be
+     * found in {@link CameraProperties}, {@link CaptureResult}, and
+     * {@link CaptureRequest}.
+     *
+     * @param key The metadata field to write.
+     * @param value The value to set the field to, which must be of a matching
+     * type to the key.
+     */
+    public <T> void set(Key<T> key, T value) {
+        int tag = key.getTag();
+
+        if (value == null) {
+            writeValues(tag, null);
+            return;
+        }
+
+        int nativeType = getNativeType(tag);
+
+        int size = packSingle(value, null, key.getType(), nativeType, /* sizeOnly */true);
+
+        // TODO: Optimization. Cache the byte[] and reuse if the size is big enough.
+        byte[] values = new byte[size];
+
+        ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
+        packSingle(value, buffer, key.getType(), nativeType, /*sizeOnly*/false);
+
+        writeValues(tag, values);
+    }
+
+    // Keep up-to-date with camera_metadata.h
+    /**
+     * @hide
+     */
+    public static final int TYPE_BYTE = 0;
+    /**
+     * @hide
+     */
+    public static final int TYPE_INT32 = 1;
+    /**
+     * @hide
+     */
+    public static final int TYPE_FLOAT = 2;
+    /**
+     * @hide
+     */
+    public static final int TYPE_INT64 = 3;
+    /**
+     * @hide
+     */
+    public static final int TYPE_DOUBLE = 4;
+    /**
+     * @hide
+     */
+    public static final int TYPE_RATIONAL = 5;
+    /**
+     * @hide
+     */
+    public static final int NUM_TYPES = 6;
+
+    private void close() {
+        // this sets mMetadataPtr to 0
+        nativeClose();
+        mMetadataPtr = 0; // set it to 0 again to prevent eclipse from making this field final
+    }
+
+    private static int getTypeSize(int nativeType) {
+        switch(nativeType) {
+            case TYPE_BYTE:
+                return 1;
+            case TYPE_INT32:
+            case TYPE_FLOAT:
+                return 4;
+            case TYPE_INT64:
+            case TYPE_DOUBLE:
+            case TYPE_RATIONAL:
+                return 8;
+        }
+
+        throw new UnsupportedOperationException("Unknown type, can't get size "
+                + nativeType);
+    }
+
+    private static Class<?> getExpectedType(int nativeType) {
+        switch(nativeType) {
+            case TYPE_BYTE:
+                return Byte.TYPE;
+            case TYPE_INT32:
+                return Integer.TYPE;
+            case TYPE_FLOAT:
+                return Float.TYPE;
+            case TYPE_INT64:
+                return Long.TYPE;
+            case TYPE_DOUBLE:
+                return Double.TYPE;
+            case TYPE_RATIONAL:
+                return Rational.class;
+        }
+
+        throw new UnsupportedOperationException("Unknown type, can't map to Java type "
+                + nativeType);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> int packSingleNative(T value, ByteBuffer buffer, Class<T> type,
+            int nativeType, boolean sizeOnly) {
+
+        if (!sizeOnly) {
+            /**
+             * Rewrite types when the native type doesn't match the managed type
+             *  - Boolean -> Byte
+             *  - Integer -> Byte
+             */
+
+            if (nativeType == TYPE_BYTE && type == Boolean.TYPE) {
+                // Since a boolean can't be cast to byte, and we don't want to use putBoolean
+                boolean asBool = (Boolean) value;
+                byte asByte = (byte) (asBool ? 1 : 0);
+                value = (T) (Byte) asByte;
+            } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) {
+                int asInt = (Integer) value;
+                byte asByte = (byte) asInt;
+                value = (T) (Byte) asByte;
+            } else if (type != getExpectedType(nativeType)) {
+                throw new UnsupportedOperationException("Tried to pack a type of " + type +
+                        " but we expected the type to be " + getExpectedType(nativeType));
+            }
+
+            if (nativeType == TYPE_BYTE) {
+                buffer.put((Byte) value);
+            } else if (nativeType == TYPE_INT32) {
+                buffer.putInt((Integer) value);
+            } else if (nativeType == TYPE_FLOAT) {
+                buffer.putFloat((Float) value);
+            } else if (nativeType == TYPE_INT64) {
+                buffer.putLong((Long) value);
+            } else if (nativeType == TYPE_DOUBLE) {
+                buffer.putDouble((Double) value);
+            } else if (nativeType == TYPE_RATIONAL) {
+                Rational r = (Rational) value;
+                buffer.putInt(r.getNumerator());
+                buffer.putInt(r.getDenominator());
+            }
+
+        }
+
+        return getTypeSize(nativeType);
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private static <T> int packSingle(T value, ByteBuffer buffer, Class<T> type, int nativeType,
+            boolean sizeOnly) {
+
+        int size = 0;
+
+        if (type.isPrimitive() || type == Rational.class) {
+            size = packSingleNative(value, buffer, type, nativeType, sizeOnly);
+        } else if (type.isEnum()) {
+            size = packEnum((Enum)value, buffer, (Class<Enum>)type, nativeType, sizeOnly);
+        } else if (type.isArray()) {
+            size = packArray(value, buffer, type, nativeType, sizeOnly);
+        } else {
+            size = packClass(value, buffer, type, nativeType, sizeOnly);
+        }
+
+        return size;
+    }
+
+    private static <T extends Enum<T>> int packEnum(T value, ByteBuffer buffer, Class<T> type,
+            int nativeType, boolean sizeOnly) {
+
+        // TODO: add support for enums with their own values.
+        return packSingleNative(getEnumValue(value), buffer, Integer.TYPE, nativeType, sizeOnly);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> int packClass(T value, ByteBuffer buffer, Class<T> type, int nativeType,
+            boolean sizeOnly) {
+
+        MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType);
+        if (marshaler == null) {
+            throw new IllegalArgumentException(String.format("Unknown Key type: %s", type));
+        }
+
+        return marshaler.marshal(value, buffer, nativeType, sizeOnly);
+    }
+
+    private static <T> int packArray(T value, ByteBuffer buffer, Class<T> type, int nativeType,
+            boolean sizeOnly) {
+
+        int size = 0;
+        int arrayLength = Array.getLength(value);
+
+        @SuppressWarnings("unchecked")
+        Class<Object> componentType = (Class<Object>)type.getComponentType();
+
+        for (int i = 0; i < arrayLength; ++i) {
+            size += packSingle(Array.get(value, i), buffer, componentType, nativeType, sizeOnly);
+        }
+
+        return size;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T unpackSingleNative(ByteBuffer buffer, Class<T> type, int nativeType) {
+
+        T val;
+
+        if (nativeType == TYPE_BYTE) {
+            val = (T) (Byte) buffer.get();
+        } else if (nativeType == TYPE_INT32) {
+            val = (T) (Integer) buffer.getInt();
+        } else if (nativeType == TYPE_FLOAT) {
+            val = (T) (Float) buffer.getFloat();
+        } else if (nativeType == TYPE_INT64) {
+            val = (T) (Long) buffer.getLong();
+        } else if (nativeType == TYPE_DOUBLE) {
+            val = (T) (Double) buffer.getDouble();
+        } else if (nativeType == TYPE_RATIONAL) {
+            val = (T) new Rational(buffer.getInt(), buffer.getInt());
+        } else {
+            throw new UnsupportedOperationException("Unknown type, can't unpack a native type "
+                + nativeType);
+        }
+
+        /**
+         * Rewrite types when the native type doesn't match the managed type
+         *  - Byte -> Boolean
+         *  - Byte -> Integer
+         */
+
+        if (nativeType == TYPE_BYTE && type == Boolean.TYPE) {
+            // Since a boolean can't be cast to byte, and we don't want to use getBoolean
+            byte asByte = (Byte) val;
+            boolean asBool = asByte != 0;
+            val = (T) (Boolean) asBool;
+        } else if (nativeType == TYPE_BYTE && type == Integer.TYPE) {
+            byte asByte = (Byte) val;
+            int asInt = asByte;
+            val = (T) (Integer) asInt;
+        } else if (type != getExpectedType(nativeType)) {
+            throw new UnsupportedOperationException("Tried to unpack a type of " + type +
+                    " but we expected the type to be " + getExpectedType(nativeType));
+        }
+
+        return val;
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private static <T> T unpackSingle(ByteBuffer buffer, Class<T> type, int nativeType) {
+
+        if (type.isPrimitive() || type == Rational.class) {
+            return unpackSingleNative(buffer, type, nativeType);
+        }
+
+        if (type.isEnum()) {
+            return (T) unpackEnum(buffer, (Class<Enum>)type, nativeType);
+        }
+
+        if (type.isArray()) {
+            return unpackArray(buffer, type, nativeType);
+        }
+
+        T instance = unpackClass(buffer, type, nativeType);
+
+        return instance;
+    }
+
+    private static <T extends Enum<T>> T unpackEnum(ByteBuffer buffer, Class<T> type,
+            int nativeType) {
+        int ordinal = unpackSingleNative(buffer, Integer.TYPE, nativeType);
+        return getEnumFromValue(type, ordinal);
+    }
+
+    private static <T> T unpackClass(ByteBuffer buffer, Class<T> type, int nativeType) {
+
+        MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType);
+        if (marshaler == null) {
+            throw new IllegalArgumentException("Unknown class type: " + type);
+        }
+
+        return marshaler.unmarshal(buffer, nativeType);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T unpackArray(ByteBuffer buffer, Class<T> type, int nativeType) {
+
+        Class<?> componentType = type.getComponentType();
+        Object array;
+
+        int elementSize = getTypeSize(nativeType);
+
+        MetadataMarshalClass<?> marshaler = getMarshaler(componentType, nativeType);
+        if (marshaler != null) {
+            elementSize = marshaler.getNativeSize(nativeType);
+        }
+
+        if (elementSize != MetadataMarshalClass.NATIVE_SIZE_DYNAMIC) {
+            int remaining = buffer.remaining();
+            int arraySize = remaining / elementSize;
+
+            Log.v(TAG,
+                    String.format(
+                            "Attempting to unpack array (count = %d, element size = %d, bytes " +
+                                    "remaining = %d) for type %s",
+                            arraySize, elementSize, remaining, type));
+
+            array = Array.newInstance(componentType, arraySize);
+            for (int i = 0; i < arraySize; ++i) {
+               Object elem = unpackSingle(buffer, componentType, nativeType);
+               Array.set(array, i, elem);
+            }
+        } else {
+            // Dynamic size, use an array list.
+            ArrayList<Object> arrayList = new ArrayList<Object>();
+
+            int primitiveSize = getTypeSize(nativeType);
+            while (buffer.remaining() >= primitiveSize) {
+                Object elem = unpackSingle(buffer, componentType, nativeType);
+                arrayList.add(elem);
+            }
+
+            array = arrayList.toArray((T[]) Array.newInstance(componentType, 0));
+        }
+
+        if (buffer.remaining() != 0) {
+            Log.e(TAG, "Trailing bytes (" + buffer.remaining() + ") left over after unpacking "
+                    + type);
+        }
+
+        return (T) array;
+    }
+
+    private long mMetadataPtr; // native CameraMetadata*
+
+    private native long nativeAllocate();
+    private native long nativeAllocateCopy(CameraMetadataNative other)
+            throws NullPointerException;
+
+    private native synchronized void nativeWriteToParcel(Parcel dest);
+    private native synchronized void nativeReadFromParcel(Parcel source);
+    private native synchronized void nativeSwap(CameraMetadataNative other)
+            throws NullPointerException;
+    private native synchronized void nativeClose();
+    private native synchronized boolean nativeIsEmpty();
+    private native synchronized int nativeGetEntryCount();
+
+    private native synchronized byte[] nativeReadValues(int tag);
+    private native synchronized void nativeWriteValues(int tag, byte[] src);
+
+    private static native int nativeGetTagFromKey(String keyName)
+            throws IllegalArgumentException;
+    private static native int nativeGetTypeFromTag(int tag)
+            throws IllegalArgumentException;
+    private static native void nativeClassInit();
+
+    /**
+     * <p>Perform a 0-copy swap of the internal metadata with another object.</p>
+     *
+     * <p>Useful to convert a CameraMetadata into e.g. a CaptureRequest.</p>
+     *
+     * @param other Metadata to swap with
+     * @throws NullPointerException if other was null
+     * @hide
+     */
+    public void swap(CameraMetadataNative other) {
+        nativeSwap(other);
+    }
+
+    /**
+     * @hide
+     */
+    public int getEntryCount() {
+        return nativeGetEntryCount();
+    }
+
+    /**
+     * Does this metadata contain at least 1 entry?
+     *
+     * @hide
+     */
+    public boolean isEmpty() {
+        return nativeIsEmpty();
+    }
+
+    /**
+     * Convert a key string into the equivalent native tag.
+     *
+     * @throws IllegalArgumentException if the key was not recognized
+     * @throws NullPointerException if the key was null
+     *
+     * @hide
+     */
+    public static int getTag(String key) {
+        return nativeGetTagFromKey(key);
+    }
+
+    /**
+     * Get the underlying native type for a tag.
+     *
+     * @param tag An integer tag, see e.g. {@link #getTag}
+     * @return An int enum for the metadata type, see e.g. {@link #TYPE_BYTE}
+     *
+     * @hide
+     */
+    public static int getNativeType(int tag) {
+        return nativeGetTypeFromTag(tag);
+    }
+
+    /**
+     * <p>Updates the existing entry for tag with the new bytes pointed by src, erasing
+     * the entry if src was null.</p>
+     *
+     * <p>An empty array can be passed in to update the entry to 0 elements.</p>
+     *
+     * @param tag An integer tag, see e.g. {@link #getTag}
+     * @param src An array of bytes, or null to erase the entry
+     *
+     * @hide
+     */
+    public void writeValues(int tag, byte[] src) {
+        nativeWriteValues(tag, src);
+    }
+
+    /**
+     * <p>Returns a byte[] of data corresponding to this tag. Use a wrapped bytebuffer to unserialize
+     * the data properly.</p>
+     *
+     * <p>An empty array can be returned to denote an existing entry with 0 elements.</p>
+     *
+     * @param tag An integer tag, see e.g. {@link #getTag}
+     *
+     * @return {@code null} if there were 0 entries for this tag, a byte[] otherwise.
+     * @hide
+     */
+    public byte[] readValues(int tag) {
+     // TODO: Optimization. Native code returns a ByteBuffer instead.
+        return nativeReadValues(tag);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static final HashMap<Class<? extends Enum>, int[]> sEnumValues =
+            new HashMap<Class<? extends Enum>, int[]>();
+    /**
+     * Register a non-sequential set of values to be used with the pack/unpack functions.
+     * This enables get/set to correctly marshal the enum into a value that is C-compatible.
+     *
+     * @param enumType The class for an enum
+     * @param values A list of values mapping to the ordinals of the enum
+     *
+     * @hide
+     */
+    public static <T extends Enum<T>> void registerEnumValues(Class<T> enumType, int[] values) {
+        if (enumType.getEnumConstants().length != values.length) {
+            throw new IllegalArgumentException(
+                    "Expected values array to be the same size as the enumTypes values "
+                            + values.length + " for type " + enumType);
+        }
+
+        Log.v(TAG, "Registered enum values for type " + enumType + " values");
+
+        sEnumValues.put(enumType, values);
+    }
+
+    /**
+     * Get the numeric value from an enum. This is usually the same as the ordinal value for
+     * enums that have fully sequential values, although for C-style enums the range of values
+     * may not map 1:1.
+     *
+     * @param enumValue Enum instance
+     * @return Int guaranteed to be ABI-compatible with the C enum equivalent
+     */
+    private static <T extends Enum<T>> int getEnumValue(T enumValue) {
+        int[] values;
+        values = sEnumValues.get(enumValue.getClass());
+
+        int ordinal = enumValue.ordinal();
+        if (values != null) {
+            return values[ordinal];
+        }
+
+        return ordinal;
+    }
+
+    /**
+     * Finds the enum corresponding to it's numeric value. Opposite of {@link #getEnumValue} method.
+     *
+     * @param enumType Class of the enum we want to find
+     * @param value The numeric value of the enum
+     * @return An instance of the enum
+     */
+    private static <T extends Enum<T>> T getEnumFromValue(Class<T> enumType, int value) {
+        int ordinal;
+
+        int[] registeredValues = sEnumValues.get(enumType);
+        if (registeredValues != null) {
+            ordinal = -1;
+
+            for (int i = 0; i < registeredValues.length; ++i) {
+                if (registeredValues[i] == value) {
+                    ordinal = i;
+                    break;
+                }
+            }
+        } else {
+            ordinal = value;
+        }
+
+        T[] values = enumType.getEnumConstants();
+
+        if (ordinal < 0 || ordinal >= values.length) {
+            throw new IllegalArgumentException(
+                    String.format(
+                            "Argument 'value' (%d) was not a valid enum value for type %s "
+                                    + "(registered? %b)",
+                            value,
+                            enumType, (registeredValues != null)));
+        }
+
+        return values[ordinal];
+    }
+
+    static HashMap<Class<?>, MetadataMarshalClass<?>> sMarshalerMap = new
+            HashMap<Class<?>, MetadataMarshalClass<?>>();
+
+    private static <T> void registerMarshaler(MetadataMarshalClass<T> marshaler) {
+        sMarshalerMap.put(marshaler.getMarshalingClass(), marshaler);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> MetadataMarshalClass<T> getMarshaler(Class<T> type, int nativeType) {
+        MetadataMarshalClass<T> marshaler = (MetadataMarshalClass<T>) sMarshalerMap.get(type);
+
+        if (marshaler != null && !marshaler.isNativeTypeSupported(nativeType)) {
+            throw new UnsupportedOperationException("Unsupported type " + nativeType +
+                    " to be marshalled to/from a " + type);
+        }
+
+        return marshaler;
+    }
+
+    /**
+     * We use a class initializer to allow the native code to cache some field offsets
+     */
+    static {
+        System.loadLibrary("media_jni");
+        nativeClassInit();
+
+        Log.v(TAG, "Shall register metadata marshalers");
+
+        // load built-in marshallers
+        registerMarshaler(new MetadataMarshalRect());
+        registerMarshaler(new MetadataMarshalSize());
+        registerMarshaler(new MetadataMarshalString());
+
+        Log.v(TAG, "Registered metadata marshalers");
+    }
+
+}
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java b/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java
index a934d75..6d224ef 100644
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java
+++ b/core/java/android/hardware/camera2/impl/MetadataMarshalClass.java
@@ -26,7 +26,7 @@
      * @param value the value of type T that we wish to write into the byte buffer
      * @param buffer the byte buffer into which the marshalled object will be written
      * @param nativeType the native type, e.g.
-     *        {@link android.hardware.camera2.CameraMetadata#TYPE_BYTE TYPE_BYTE}.
+     *        {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}.
      *        Guaranteed to be one for which isNativeTypeSupported returns true.
      * @param sizeOnly if this is true, don't write to the byte buffer. calculate the size only.
      * @return the size that needs to be written to the byte buffer
@@ -37,7 +37,7 @@
      * Unmarshal a new object instance from the byte buffer.
      * @param buffer the byte buffer, from which we will read the object
      * @param nativeType the native type, e.g.
-     *        {@link android.hardware.camera2.CameraMetadata#TYPE_BYTE TYPE_BYTE}.
+     *        {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}.
      *        Guaranteed to be one for which isNativeTypeSupported returns true.
      * @return a new instance of type T read from the byte buffer
      */
@@ -50,7 +50,7 @@
      * will are likely to only support one type.
      *
      * @param nativeType the native type, e.g.
-     *        {@link android.hardware.camera2.CameraMetadata#TYPE_BYTE TYPE_BYTE}
+     *        {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}
      * @return true if it supports, false otherwise
      */
     boolean isNativeTypeSupported(int nativeType);
@@ -60,7 +60,7 @@
     /**
      * How many bytes T will take up if marshalled to/from nativeType
      * @param nativeType the native type, e.g.
-     *        {@link android.hardware.camera2.CameraMetadata#TYPE_BYTE TYPE_BYTE}
+     *        {@link android.hardware.camera2.impl.CameraMetadataNative#TYPE_BYTE TYPE_BYTE}
      * @return a size in bytes, or NATIVE_SIZE_DYNAMIC if the size is dynamic
      */
     int getNativeSize(int nativeType);
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java b/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java
index 384223c..ab72c4f 100644
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java
+++ b/core/java/android/hardware/camera2/impl/MetadataMarshalRect.java
@@ -16,7 +16,6 @@
 package android.hardware.camera2.impl;
 
 import android.graphics.Rect;
-import android.hardware.camera2.CameraMetadata;
 
 import java.nio.ByteBuffer;
 
@@ -58,7 +57,7 @@
 
     @Override
     public boolean isNativeTypeSupported(int nativeType) {
-        return nativeType == CameraMetadata.TYPE_INT32;
+        return nativeType == CameraMetadataNative.TYPE_INT32;
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java b/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java
index 793bba7..e8143e0 100644
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java
+++ b/core/java/android/hardware/camera2/impl/MetadataMarshalSize.java
@@ -15,7 +15,6 @@
  */
 package android.hardware.camera2.impl;
 
-import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.Size;
 
 import java.nio.ByteBuffer;
@@ -51,7 +50,7 @@
 
     @Override
     public boolean isNativeTypeSupported(int nativeType) {
-        return nativeType == CameraMetadata.TYPE_INT32;
+        return nativeType == CameraMetadataNative.TYPE_INT32;
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/impl/MetadataMarshalString.java b/core/java/android/hardware/camera2/impl/MetadataMarshalString.java
index 50b3347..b61b8d3 100644
--- a/core/java/android/hardware/camera2/impl/MetadataMarshalString.java
+++ b/core/java/android/hardware/camera2/impl/MetadataMarshalString.java
@@ -15,8 +15,6 @@
  */
 package android.hardware.camera2.impl;
 
-import android.hardware.camera2.CameraMetadata;
-
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 
@@ -71,7 +69,7 @@
 
     @Override
     public boolean isNativeTypeSupported(int nativeType) {
-        return nativeType == CameraMetadata.TYPE_BYTE;
+        return nativeType == CameraMetadataNative.TYPE_BYTE;
     }
 
     @Override
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 852c4d4..3c7da1e 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -38,7 +38,7 @@
 #endif
 
 // fully-qualified class name
-#define CAMERA_METADATA_CLASS_NAME "android/hardware/camera2/CameraMetadata"
+#define CAMERA_METADATA_CLASS_NAME "android/hardware/camera2/impl/CameraMetadataNative"
 
 using namespace android;
 
@@ -152,6 +152,21 @@
     return reinterpret_cast<jlong>(new CameraMetadata());
 }
 
+static jlong CameraMetadata_allocateCopy(JNIEnv *env, jobject thiz,
+        jobject other) {
+    ALOGV("%s", __FUNCTION__);
+
+    CameraMetadata* otherMetadata =
+            CameraMetadata_getPointerThrow(env, other, "other");
+
+    // In case of exception, return
+    if (otherMetadata == NULL) return NULL;
+
+    // Clone native metadata and return new pointer
+    return reinterpret_cast<jlong>(new CameraMetadata(*otherMetadata));
+}
+
+
 static jboolean CameraMetadata_isEmpty(JNIEnv *env, jobject thiz) {
     ALOGV("%s", __FUNCTION__);
 
@@ -361,6 +376,9 @@
   { "nativeAllocate",
     "()J",
     (void*)CameraMetadata_allocate },
+  { "nativeAllocateCopy",
+    "(L" CAMERA_METADATA_CLASS_NAME ";)J",
+    (void *)CameraMetadata_allocateCopy },
   { "nativeIsEmpty",
     "()Z",
     (void*)CameraMetadata_isEmpty },
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 624bbaa..1b7faec 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -25,6 +25,7 @@
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.utils.BinderHolder;
 import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.os.Binder;
@@ -155,7 +156,7 @@
         }
 
         @Override
-        public void onResultReceived(int frameId, CameraMetadata result) throws RemoteException {
+        public void onResultReceived(int frameId, CameraMetadataNative result) throws RemoteException {
         }
     }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 2f271bb..56d73c0 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -22,6 +22,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.utils.BinderHolder;
 import android.os.RemoteException;
 import android.test.AndroidTestCase;
@@ -62,13 +63,13 @@
         }
 
         @Override
-        public void onResultReceived(int frameId, CameraMetadata result) throws RemoteException {
+        public void onResultReceived(int frameId, CameraMetadataNative result) throws RemoteException {
         }
     }
 
-    class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadata> {
+    class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadataNative> {
         public boolean matches(Object obj) {
-            return !((CameraMetadata) obj).isEmpty();
+            return !((CameraMetadataNative) obj).isEmpty();
         }
      }
 
@@ -78,20 +79,17 @@
         mSurface = new Surface(mSurfaceTexture);
     }
 
-    private CaptureRequest createDefaultRequest(boolean needStream) throws Exception {
-        CameraMetadata metadata = new CameraMetadata();
+    private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception {
+        CameraMetadataNative metadata = new CameraMetadataNative();
         assertTrue(metadata.isEmpty());
 
-        CaptureRequest request = new CaptureRequest();
-        assertTrue(request.isEmpty());
-
         int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
         assertEquals(CameraBinderTestUtils.NO_ERROR, status);
         assertFalse(metadata.isEmpty());
 
-        request.swap(metadata);
+        CaptureRequest.Builder request = new CaptureRequest.Builder(metadata);
         assertFalse(request.isEmpty());
-        assertTrue(metadata.isEmpty());
+        assertFalse(metadata.isEmpty());
         if (needStream) {
             int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20,
                     /* ignored */30, mSurface);
@@ -150,14 +148,13 @@
 
     @SmallTest
     public void testCreateDefaultRequest() throws Exception {
-        CameraMetadata metadata = new CameraMetadata();
+        CameraMetadataNative metadata = new CameraMetadataNative();
         assertTrue(metadata.isEmpty());
 
         int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
         assertEquals(CameraBinderTestUtils.NO_ERROR, status);
         assertFalse(metadata.isEmpty());
 
-        metadata.close();
     }
 
     @SmallTest
@@ -208,37 +205,39 @@
     @SmallTest
     public void testSubmitBadRequest() throws Exception {
 
-        CaptureRequest request = createDefaultRequest(/* needStream */false);
-        int status = mCameraUser.submitRequest(request, /* streaming */false);
+        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false);
+        CaptureRequest request1 = builder.build();
+        int status = mCameraUser.submitRequest(request1, /* streaming */false);
         assertEquals("Expected submitRequest to return BAD_VALUE " +
                 "since we had 0 surface targets set.", CameraBinderTestUtils.BAD_VALUE, status);
 
-        request.addTarget(mSurface);
-        status = mCameraUser.submitRequest(request, /* streaming */false);
+        builder.addTarget(mSurface);
+        CaptureRequest request2 = builder.build();
+        status = mCameraUser.submitRequest(request2, /* streaming */false);
         assertEquals("Expected submitRequest to return BAD_VALUE since " +
                 "the target surface wasn't registered with createStream.",
                 CameraBinderTestUtils.BAD_VALUE, status);
-
-        request.close();
     }
 
     @SmallTest
     public void testSubmitGoodRequest() throws Exception {
 
-        CaptureRequest request = createDefaultRequest(/* needStream */true);
+        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
+        CaptureRequest request = builder.build();
 
         // Submit valid request twice.
         int requestId1 = submitCameraRequest(request, /* streaming */false);
         int requestId2 = submitCameraRequest(request, /* streaming */false);
         assertNotSame("Request IDs should be unique for multiple requests", requestId1, requestId2);
 
-        request.close();
     }
 
     @SmallTest
     public void testSubmitStreamingRequest() throws Exception {
 
-        CaptureRequest request = createDefaultRequest(/* needStream */true);
+        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
+
+        CaptureRequest request = builder.build();
 
         // Submit valid request once (non-streaming), and another time
         // (streaming)
@@ -260,12 +259,11 @@
         assertEquals("Streaming request IDs should be cancellable", CameraBinderTestUtils.NO_ERROR,
                 status);
 
-        request.close();
     }
 
     @SmallTest
     public void testCameraInfo() throws RemoteException {
-        CameraMetadata info = new CameraMetadata();
+        CameraMetadataNative info = new CameraMetadataNative();
 
         int status = mCameraUser.getCameraInfo(/*out*/info);
         assertEquals(CameraBinderTestUtils.NO_ERROR, status);
@@ -276,8 +274,8 @@
 
     @SmallTest
     public void testWaitUntilIdle() throws Exception {
-        CaptureRequest request = createDefaultRequest(/* needStream */true);
-        int requestIdStreaming = submitCameraRequest(request, /* streaming */true);
+        CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
+        int requestIdStreaming = submitCameraRequest(builder.build(), /* streaming */true);
 
         // Test Bad case first: waitUntilIdle when there is active repeating request
         int status = mCameraUser.waitUntilIdle();
@@ -294,7 +292,7 @@
     @SmallTest
     public void testCaptureResultCallbacks() throws Exception {
         IsMetadataNotEmpty matcher = new IsMetadataNotEmpty();
-        CaptureRequest request = createDefaultRequest(/* needStream */true);
+        CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
 
         // Test both single request and streaming request.
         int requestId1 = submitCameraRequest(request, /* streaming */false);
@@ -307,7 +305,6 @@
                 .onResultReceived(
                         eq(streamingId),
                         argThat(matcher));
-        request.close();
     }
 
     @SmallTest
@@ -319,7 +316,7 @@
         assertEquals(CameraBinderTestUtils.NO_ERROR, status);
 
         // Then set up a stream
-        CaptureRequest request = createDefaultRequest(/* needStream */true);
+        CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
 
         // Flush should still be a no-op, really
         status = mCameraUser.flush();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index ecf01d9..874e078 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -23,8 +23,9 @@
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.Rational;
 import android.hardware.camera2.Size;
+import android.hardware.camera2.impl.CameraMetadataNative;
 
-import static android.hardware.camera2.CameraMetadata.*;
+import static android.hardware.camera2.impl.CameraMetadataNative.*;
 
 import java.lang.reflect.Array;
 import java.nio.ByteBuffer;
@@ -42,7 +43,7 @@
  */
 public class CameraMetadataTest extends junit.framework.TestCase {
 
-    CameraMetadata mMetadata;
+    CameraMetadataNative mMetadata;
     Parcel mParcel;
 
     // Sections
@@ -62,13 +63,12 @@
 
     @Override
     public void setUp() {
-        mMetadata = new CameraMetadata();
+        mMetadata = new CameraMetadataNative();
         mParcel = Parcel.obtain();
     }
 
     @Override
     public void tearDown() throws Exception {
-        mMetadata.close();
         mMetadata = null;
 
         mParcel.recycle();
@@ -82,115 +82,47 @@
     }
 
     @SmallTest
-    public void testClose() throws Exception {
-        mMetadata.isEmpty(); // no throw
-
-        assertFalse(mMetadata.isClosed());
-
-        mMetadata.close();
-
-        assertTrue(mMetadata.isClosed());
-
-        // OK: second close should not throw
-        mMetadata.close();
-
-        assertTrue(mMetadata.isClosed());
-
-        // All other calls after close should throw IllegalStateException
-
-        try {
-            mMetadata.isEmpty();
-            fail("Unreachable -- isEmpty after close should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-            // good: we expect calling this method after close to fail
-        }
-
-        try {
-            mMetadata.getEntryCount();
-            fail("Unreachable -- getEntryCount after close should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-            // good: we expect calling this method after close to fail
-        }
-
-
-        try {
-            mMetadata.swap(mMetadata);
-            fail("Unreachable -- swap after close should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-         // good: we expect calling this method after close to fail
-        }
-
-        try {
-            mMetadata.readFromParcel(mParcel);
-            fail("Unreachable -- readFromParcel after close should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-         // good: we expect calling this method after close to fail
-        }
-
-        try {
-            mMetadata.writeToParcel(mParcel, /*flags*/0);
-            fail("Unreachable -- writeToParcel after close should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-         // good: we expect calling this method after close to fail
-        }
-
-        try {
-            mMetadata.readValues(/*tag*/0);
-            fail("Unreachable -- readValues after close should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-         // good: we expect calling this method after close to fail
-        }
-
-        try {
-            mMetadata.writeValues(/*tag*/0, /*source*/new byte[] { 1,2,3 });
-            fail("Unreachable -- readValues after close should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-         // good: we expect calling this method after close to fail
-        }
-    }
-
-    @SmallTest
     public void testGetTagFromKey() {
 
         // Test success
 
         assertEquals(ANDROID_COLOR_CORRECTION_MODE,
-                CameraMetadata.getTag("android.colorCorrection.mode"));
+                CameraMetadataNative.getTag("android.colorCorrection.mode"));
         assertEquals(ANDROID_COLOR_CORRECTION_TRANSFORM,
-                CameraMetadata.getTag("android.colorCorrection.transform"));
+                CameraMetadataNative.getTag("android.colorCorrection.transform"));
         assertEquals(ANDROID_CONTROL_AE_ANTIBANDING_MODE,
-                CameraMetadata.getTag("android.control.aeAntibandingMode"));
+                CameraMetadataNative.getTag("android.control.aeAntibandingMode"));
         assertEquals(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
-                CameraMetadata.getTag("android.control.aeExposureCompensation"));
+                CameraMetadataNative.getTag("android.control.aeExposureCompensation"));
 
         // Test failures
 
         try {
-            CameraMetadata.getTag(null);
+            CameraMetadataNative.getTag(null);
             fail("A null key should throw NPE");
         } catch(NullPointerException e) {
         }
 
         try {
-            CameraMetadata.getTag("android.control");
+            CameraMetadataNative.getTag("android.control");
             fail("A section name only should not be a valid key");
         } catch(IllegalArgumentException e) {
         }
 
         try {
-            CameraMetadata.getTag("android.control.thisTagNameIsFakeAndDoesNotExist");
+            CameraMetadataNative.getTag("android.control.thisTagNameIsFakeAndDoesNotExist");
             fail("A valid section with an invalid tag name should not be a valid key");
         } catch(IllegalArgumentException e) {
         }
 
         try {
-            CameraMetadata.getTag("android");
+            CameraMetadataNative.getTag("android");
             fail("A namespace name only should not be a valid key");
         } catch(IllegalArgumentException e) {
         }
 
         try {
-            CameraMetadata.getTag("this.key.is.definitely.invalid");
+            CameraMetadataNative.getTag("this.key.is.definitely.invalid");
             fail("A completely fake key name should not be valid");
         } catch(IllegalArgumentException e) {
         }
@@ -198,14 +130,14 @@
 
     @SmallTest
     public void testGetTypeFromTag() {
-        assertEquals(TYPE_BYTE, CameraMetadata.getNativeType(ANDROID_COLOR_CORRECTION_MODE));
-        assertEquals(TYPE_FLOAT, CameraMetadata.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM));
-        assertEquals(TYPE_BYTE, CameraMetadata.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE));
+        assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE));
+        assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM));
+        assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE));
         assertEquals(TYPE_INT32,
-                CameraMetadata.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION));
+                CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION));
 
         try {
-            CameraMetadata.getNativeType(0xDEADF00D);
+            CameraMetadataNative.getNativeType(0xDEADF00D);
             fail("No type should exist for invalid tag 0xDEADF00D");
         } catch(IllegalArgumentException e) {
         }
@@ -454,7 +386,7 @@
 
     @SmallTest
     public void testReadWriteEnumWithCustomValues() {
-        CameraMetadata.registerEnumValues(AeAntibandingMode.class, new int[] {
+        CameraMetadataNative.registerEnumValues(AeAntibandingMode.class, new int[] {
             0,
             10,
             20,
@@ -475,7 +407,7 @@
         Key<AeAntibandingMode[]> aeAntibandingModeKey =
                 new Key<AeAntibandingMode[]>("android.control.aeAvailableAntibandingModes",
                         AeAntibandingMode[].class);
-        byte[] aeAntibandingModeValues = mMetadata.readValues(CameraMetadata
+        byte[] aeAntibandingModeValues = mMetadata.readValues(CameraMetadataNative
                 .getTag("android.control.aeAvailableAntibandingModes"));
         byte[] expectedValues = new byte[] { 0, 10, 20, 30 };
         assertArrayEquals(expectedValues, aeAntibandingModeValues);
@@ -485,7 +417,7 @@
          * Stranger cases that don't use byte enums
          */
         // int (n)
-        CameraMetadata.registerEnumValues(AvailableFormat.class, new int[] {
+        CameraMetadataNative.registerEnumValues(AvailableFormat.class, new int[] {
             0x20,
             0x32315659,
             0x11,
@@ -505,7 +437,7 @@
         Key<AeAntibandingMode> availableFormatsKey =
                 new Key<AeAntibandingMode>("android.scaler.availableFormats",
                         AeAntibandingMode.class);
-        byte[] availableFormatValues = mMetadata.readValues(CameraMetadata
+        byte[] availableFormatValues = mMetadata.readValues(CameraMetadataNative
                 .getTag(availableFormatsKey.getName()));
 
         int[] expectedIntValues = new int[] {