camera2 api: marshal rect, size, string for metadata

Change-Id: Iec2fd823bc92394da44abb32ca38a625d7638e3d
diff --git a/core/java/android/hardware/photography/CameraMetadata.java b/core/java/android/hardware/photography/CameraMetadata.java
index 4633b2f..c024c05 100644
--- a/core/java/android/hardware/photography/CameraMetadata.java
+++ b/core/java/android/hardware/photography/CameraMetadata.java
@@ -16,6 +16,10 @@
 
 package android.hardware.photography;
 
+import android.hardware.photography.impl.MetadataMarshalClass;
+import android.hardware.photography.impl.MetadataMarshalRect;
+import android.hardware.photography.impl.MetadataMarshalSize;
+import android.hardware.photography.impl.MetadataMarshalString;
 import android.os.Parcelable;
 import android.os.Parcel;
 import android.util.Log;
@@ -85,6 +89,11 @@
     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);
@@ -265,6 +274,11 @@
     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) {
+            return marshaler.marshal(value, buffer, nativeType, sizeOnly);
+        }
+
         /**
          * FIXME: This doesn't actually work because getFields() returns fields in an unordered
          * manner. Although we could sort and get the data to come out correctly on the *java* side,
@@ -558,6 +572,11 @@
 
     private static <T> T unpackClass(ByteBuffer buffer, Class<T> type, int nativeType) {
 
+        MetadataMarshalClass<T> marshaler = getMarshaler(type, nativeType);
+        if (marshaler != null) {
+            return marshaler.unmarshal(buffer, nativeType);
+        }
+
         /**
          * FIXME: This doesn't actually work because getFields() returns fields in an unordered
          * manner. Although we could sort and get the data to come out correctly on the *java* side,
@@ -611,14 +630,44 @@
         Class<?> componentType = type.getComponentType();
         Object array;
 
-        int remaining = buffer.remaining();
-        // FIXME: Assumes that the rest of the ByteBuffer is part of the array.
-        int arraySize = remaining / getTypeSize(nativeType);
+        int elementSize = getTypeSize(nativeType);
 
-        array = Array.newInstance(componentType, arraySize);
-        for (int i = 0; i < arraySize; ++i) {
-           Object elem = unpackSingle(buffer, componentType, nativeType);
-           Array.set(array, i, elem);
+        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;
@@ -927,11 +976,39 @@
         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/photography/impl/MetadataMarshalClass.java b/core/java/android/hardware/photography/impl/MetadataMarshalClass.java
new file mode 100644
index 0000000..a70784d
--- /dev/null
+++ b/core/java/android/hardware/photography/impl/MetadataMarshalClass.java
@@ -0,0 +1,67 @@
+/*
+ * 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.photography.impl;
+
+import java.nio.ByteBuffer;
+
+public interface MetadataMarshalClass<T> {
+
+    /**
+     * Marshal the specified object instance (value) into a byte buffer.
+     *
+     * @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.photography.CameraMetadata#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
+     */
+    int marshal(T value, ByteBuffer buffer, int nativeType, boolean sizeOnly);
+
+    /**
+     * 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.photography.CameraMetadata#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
+     */
+    T unmarshal(ByteBuffer buffer, int nativeType);
+
+    Class<T> getMarshalingClass();
+
+    /**
+     * Determines whether or not this marshaller supports this native type. Most marshallers
+     * will are likely to only support one type.
+     *
+     * @param nativeType the native type, e.g.
+     *        {@link android.hardware.photography.CameraMetadata#TYPE_BYTE TYPE_BYTE}
+     * @return true if it supports, false otherwise
+     */
+    boolean isNativeTypeSupported(int nativeType);
+
+    public static int NATIVE_SIZE_DYNAMIC = -1;
+
+    /**
+     * How many bytes T will take up if marshalled to/from nativeType
+     * @param nativeType the native type, e.g.
+     *        {@link android.hardware.photography.CameraMetadata#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/photography/impl/MetadataMarshalRect.java b/core/java/android/hardware/photography/impl/MetadataMarshalRect.java
new file mode 100644
index 0000000..d6636ac
--- /dev/null
+++ b/core/java/android/hardware/photography/impl/MetadataMarshalRect.java
@@ -0,0 +1,68 @@
+/*
+ * 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.photography.impl;
+
+import android.graphics.Rect;
+import android.hardware.photography.CameraMetadata;
+
+import java.nio.ByteBuffer;
+
+public class MetadataMarshalRect implements MetadataMarshalClass<Rect> {
+    private static final int SIZE = 16;
+
+    @Override
+    public int marshal(Rect value, ByteBuffer buffer, int nativeType, boolean sizeOnly) {
+        if (sizeOnly) {
+            return SIZE;
+        }
+
+        buffer.putInt(value.left);
+        buffer.putInt(value.top);
+        buffer.putInt(value.width());
+        buffer.putInt(value.height());
+
+        return SIZE;
+    }
+
+    @Override
+    public Rect unmarshal(ByteBuffer buffer, int nativeType) {
+
+        int left = buffer.getInt();
+        int top = buffer.getInt();
+        int width = buffer.getInt();
+        int height = buffer.getInt();
+
+        int right = left + width;
+        int bottom = top + height;
+
+        return new Rect(left, top, right, bottom);
+    }
+
+    @Override
+    public Class<Rect> getMarshalingClass() {
+        return Rect.class;
+    }
+
+    @Override
+    public boolean isNativeTypeSupported(int nativeType) {
+        return nativeType == CameraMetadata.TYPE_INT32;
+    }
+
+    @Override
+    public int getNativeSize(int nativeType) {
+        return SIZE;
+    }
+}
diff --git a/core/java/android/hardware/photography/impl/MetadataMarshalSize.java b/core/java/android/hardware/photography/impl/MetadataMarshalSize.java
new file mode 100644
index 0000000..430219c
--- /dev/null
+++ b/core/java/android/hardware/photography/impl/MetadataMarshalSize.java
@@ -0,0 +1,61 @@
+/*
+ * 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.photography.impl;
+
+import android.hardware.photography.CameraMetadata;
+import android.hardware.photography.Size;
+
+import java.nio.ByteBuffer;
+
+public class MetadataMarshalSize implements MetadataMarshalClass<Size> {
+
+    private static final int SIZE = 8;
+
+    @Override
+    public int marshal(Size value, ByteBuffer buffer, int nativeType, boolean sizeOnly) {
+        if (sizeOnly) {
+            return SIZE;
+        }
+
+        buffer.putInt(value.getWidth());
+        buffer.putInt(value.getHeight());
+
+        return SIZE;
+    }
+
+    @Override
+    public Size unmarshal(ByteBuffer buffer, int nativeType) {
+        int width = buffer.getInt();
+        int height = buffer.getInt();
+
+        return new Size(width, height);
+    }
+
+    @Override
+    public Class<Size> getMarshalingClass() {
+        return Size.class;
+    }
+
+    @Override
+    public boolean isNativeTypeSupported(int nativeType) {
+        return nativeType == CameraMetadata.TYPE_INT32;
+    }
+
+    @Override
+    public int getNativeSize(int nativeType) {
+        return SIZE;
+    }
+}
diff --git a/core/java/android/hardware/photography/impl/MetadataMarshalString.java b/core/java/android/hardware/photography/impl/MetadataMarshalString.java
new file mode 100644
index 0000000..81123ee
--- /dev/null
+++ b/core/java/android/hardware/photography/impl/MetadataMarshalString.java
@@ -0,0 +1,81 @@
+/*
+ * 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.photography.impl;
+
+import android.hardware.photography.CameraMetadata;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+public class MetadataMarshalString implements MetadataMarshalClass<String> {
+
+    private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+
+    @Override
+    public int marshal(String value, ByteBuffer buffer, int nativeType, boolean sizeOnly) {
+        byte[] arr = value.getBytes(UTF8_CHARSET);
+
+        if (!sizeOnly) {
+            buffer.put(arr);
+            buffer.put((byte)0); // metadata strings are NULL-terminated
+        }
+
+        return arr.length + 1;
+    }
+
+    @Override
+    public String unmarshal(ByteBuffer buffer, int nativeType) {
+
+        buffer.mark(); // save the current position
+
+        boolean foundNull = false;
+        int stringLength = 0;
+        while (buffer.hasRemaining()) {
+            if (buffer.get() == (byte)0) {
+                foundNull = true;
+                break;
+            }
+
+            stringLength++;
+        }
+        if (!foundNull) {
+            throw new IllegalArgumentException("Strings must be null-terminated");
+        }
+
+        buffer.reset(); // go back to the previously marked position
+
+        byte[] strBytes = new byte[stringLength + 1];
+        buffer.get(strBytes, /*dstOffset*/0, stringLength + 1); // including null character
+
+        // not including null character
+        return new String(strBytes, /*offset*/0, stringLength, UTF8_CHARSET);
+    }
+
+    @Override
+    public Class<String> getMarshalingClass() {
+        return String.class;
+    }
+
+    @Override
+    public boolean isNativeTypeSupported(int nativeType) {
+        return nativeType == CameraMetadata.TYPE_BYTE;
+    }
+
+    @Override
+    public int getNativeSize(int nativeType) {
+        return NATIVE_SIZE_DYNAMIC;
+    }
+}
diff --git a/core/jni/android_hardware_photography_CameraMetadata.cpp b/core/jni/android_hardware_photography_CameraMetadata.cpp
index 5070d2c..5190a37 100644
--- a/core/jni/android_hardware_photography_CameraMetadata.cpp
+++ b/core/jni/android_hardware_photography_CameraMetadata.cpp
@@ -267,7 +267,13 @@
 
     if (src == NULL) {
         // If array is NULL, delete the entry
-        res = metadata->erase(tag);
+        if (metadata->exists(tag)) {
+            res = metadata->erase(tag);
+            ALOGV("%s: Erase values (res = %d)", __FUNCTION__, res);
+        } else {
+            res = OK;
+            ALOGV("%s: Don't need to erase", __FUNCTION__);
+        }
     } else {
         // Copy from java array into native array
         ScopedByteArrayRO arrayReader(env, src);
@@ -275,6 +281,8 @@
 
         res = Helpers::updateAny(metadata, static_cast<uint32_t>(tag),
                                  tagType, arrayReader.get(), arrayReader.size());
+
+        ALOGV("%s: Update values (res = %d)", __FUNCTION__, res);
     }
 
     if (res == OK) {
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 0c2f3a3..131441b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -19,8 +19,10 @@
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.graphics.ImageFormat;
+import android.graphics.Rect;
 import android.hardware.photography.CameraMetadata;
 import android.hardware.photography.Rational;
+import android.hardware.photography.Size;
 
 import static android.hardware.photography.CameraMetadata.*;
 
@@ -223,6 +225,10 @@
 
         assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE));
 
+        // Write/read null values
+        mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, null);
+        assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE));
+
         // Write 0 values
         mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] {});
 
@@ -286,13 +292,21 @@
     }
 
     private <T> void checkKeyGetAndSet(String keyStr, Class<T> type, T value) {
+        assertFalse("Use checkKeyGetAndSetArray to compare array Keys", type.isArray());
+
         Key<T> key = new Key<T>(keyStr, type);
         assertNull(mMetadata.get(key));
+        mMetadata.set(key, null);
+        assertNull(mMetadata.get(key));
         mMetadata.set(key, value);
-        assertEquals(value, mMetadata.get(key));
+
+        T actual = mMetadata.get(key);
+        assertEquals(value, actual);
     }
 
     private <T> void checkKeyGetAndSetArray(String keyStr, Class<T> type, T value) {
+        assertTrue(type.isArray());
+
         Key<T> key = new Key<T>(keyStr, type);
         assertNull(mMetadata.get(key));
         mMetadata.set(key, value);
@@ -508,4 +522,73 @@
             assertEquals(expectedIntValues[i], bf.getInt());
         }
     }
+
+    @SmallTest
+    public void testReadWriteSize() {
+        // int32 x n
+        checkKeyGetAndSet("android.jpeg.thumbnailSize", Size.class, new Size(123, 456));
+
+        // int32 x 2 x n
+        checkKeyGetAndSetArray("android.scaler.availableJpegSizes", Size[].class, new Size[] {
+            new Size(123, 456),
+            new Size(0xDEAD, 0xF00D),
+            new Size(0xF00, 0xB00)
+        });
+    }
+
+    @SmallTest
+    public void testReadWriteRectangle() {
+        // int32 x n
+        checkKeyGetAndSet("android.scaler.cropRegion", Rect.class, new Rect(10, 11, 1280, 1024));
+
+        // int32 x 2 x n
+        checkKeyGetAndSetArray("android.statistics.faceRectangles", Rect[].class, new Rect[] {
+            new Rect(110, 111, 11280, 11024),
+            new Rect(210, 111, 21280, 21024),
+            new Rect(310, 111, 31280, 31024)
+        });
+    }
+
+    @SmallTest
+    public void testReadWriteString() {
+        // (byte) string
+        Key<String> gpsProcessingMethodKey =
+                new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
+
+        String helloWorld = new String("HelloWorld");
+        byte[] helloWorldBytes = new byte[] {
+                'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0' };
+
+        mMetadata.set(gpsProcessingMethodKey, helloWorld);
+
+        String actual = mMetadata.get(gpsProcessingMethodKey);
+        assertEquals(helloWorld, actual);
+
+        byte[] actualBytes = mMetadata.readValues(getTag(gpsProcessingMethodKey.getName()));
+        assertArrayEquals(helloWorldBytes, actualBytes);
+
+        // Does not yet test as a string[] since we don't support that in native code.
+
+        // (byte) string
+        Key<String[]> gpsProcessingMethodKeyArray =
+                new Key<String[]>("android.jpeg.gpsProcessingMethod", String[].class);
+
+        String[] gpsStrings = new String[] { "HelloWorld", "FooBar", "Shazbot" };
+        byte[] gpsBytes = new byte[] {
+                'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0',
+                'F', 'o', 'o', 'B', 'a', 'r', '\0',
+                'S', 'h', 'a', 'z', 'b', 'o', 't', '\0'};
+
+        mMetadata.set(gpsProcessingMethodKeyArray, gpsStrings);
+
+        String[] actualArray = mMetadata.get(gpsProcessingMethodKeyArray);
+        assertArrayEquals(gpsStrings, actualArray);
+
+        byte[] actualBytes2 = mMetadata.readValues(getTag(gpsProcessingMethodKeyArray.getName()));
+        assertArrayEquals(gpsBytes, actualBytes2);
+    }
+
+    <T> void compareGeneric(T expected, T actual) {
+        assertEquals(expected, actual);
+    }
 }