OSDN Git Service

Camera2: add metadata override
authorZhijun He <zhijunhe@google.com>
Wed, 2 Oct 2013 18:39:43 +0000 (11:39 -0700)
committerZhijun He <zhijunhe@google.com>
Mon, 7 Oct 2013 15:02:25 +0000 (08:02 -0700)
There are interface difference between managed and native side for some
metadata, like Face and JPEG format. we need override them when they are set/get
between native and managed sides.

Also fix some issues in the CameraMetadataTest.

Bug: 10406212

Change-Id: Ibd8be0415968445e711b3ea89f45e1edfa193ee2

core/java/android/hardware/camera2/CaptureResult.java
core/java/android/hardware/camera2/Face.java
core/java/android/hardware/camera2/impl/CameraMetadataNative.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java

index c9626f1..30bffc4 100644 (file)
@@ -60,11 +60,6 @@ public final class CaptureResult extends CameraMetadata {
 
     @Override
     public <T> T get(Key<T> key) {
-        if (key == STATISTICS_FACES) { // Don't throw IllegalArgumentException
-            // TODO: Implement android.statistics.faces
-            return null;
-        }
-
         return mResults.get(key);
     }
 
index ef068ca..4ac04dd 100644 (file)
@@ -58,6 +58,9 @@ public final class Face {
      * Create a new face with all fields set.
      *
      * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional.
+     * They are only required when the {@link #CaptureResult} reports that the value of key
+     * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is
+     * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}.
      * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
      * rightEyePosition, and mouthPosition may be independently null or not-null.</p>
@@ -107,7 +110,11 @@ public final class Face {
      * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional.
      * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
-     * rightEyePosition, and mouthPosition may be independently null or not-null.</p>
+     * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
+     * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
+     * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult},
+     * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition,
+     * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p>
      *
      * @param bounds Bounds of the face.
      * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
@@ -168,7 +175,10 @@ public final class Face {
      * <p>This is an optional field, may not be supported on all devices.
      * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
-     * rightEyePosition, and mouthPosition may be independently null or not-null.</p>
+     * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
+     * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
+     * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult},
+     * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p>
      *
      * <p>This value will either be {@value #ID_UNSUPPORTED} or
      * otherwise greater than {@code 0}.</p>
@@ -219,7 +229,7 @@ public final class Face {
      * field, may not be supported on all devices. If not
      * supported, the value will always be set to null.
      * This value will  always be null only if {@link #getId()} returns
-     * {@value #ID_UNSUPPORTED}.</p> them are.
+     * {@value #ID_UNSUPPORTED}.</p>
      * </p>
      *
      * @return The mouth position, or {@code null} if unknown.
index 89ffd93..eb82ed4 100644 (file)
 
 package android.hardware.camera2.impl;
 
+import android.graphics.ImageFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.Face;
 import android.hardware.camera2.Rational;
 import android.os.Parcelable;
 import android.os.Parcel;
@@ -36,6 +42,8 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
 
     private static final String TAG = "CameraMetadataJV";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    // this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h
+    private static final int NATIVE_JPEG_FORMAT = 0x21;
 
     public CameraMetadataNative() {
         super();
@@ -84,16 +92,12 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
     @SuppressWarnings("unchecked")
     @Override
     public <T> T get(Key<T> key) {
-        int tag = key.getTag();
-        byte[] values = readValues(tag);
-        if (values == null) {
-            return null;
+        T value = getOverride(key);
+        if (value != null) {
+            return value;
         }
 
-        int nativeType = getNativeType(tag);
-
-        ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
-        return unpackSingle(buffer, key.getType(), nativeType);
+        return getBase(key);
     }
 
     public void readFromParcel(Parcel in) {
@@ -110,24 +114,11 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
      * type to the key.
      */
     public <T> void set(Key<T> key, T value) {
-        int tag = key.getTag();
-
-        if (value == null) {
-            writeValues(tag, null);
+        if (setOverride(key, value)) {
             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);
+        setBase(key, value);
     }
 
     // Keep up-to-date with camera_metadata.h
@@ -435,6 +426,157 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
         return (T) array;
     }
 
+    private <T> T getBase(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);
+    }
+
+    // Need overwrite some metadata that has different definitions between native
+    // and managed sides.
+    @SuppressWarnings("unchecked")
+    private <T> T getOverride(Key<T> key) {
+        if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) {
+            return (T) getAvailableFormats();
+        } else if (key == CaptureResult.STATISTICS_FACES) {
+            return (T) getFaces();
+        }
+
+        // For other keys, get() falls back to getBase()
+        return null;
+    }
+
+    private int[] getAvailableFormats() {
+        int[] availableFormats = getBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
+        for (int i = 0; i < availableFormats.length; i++) {
+            // JPEG has different value between native and managed side, need override.
+            if (availableFormats[i] == NATIVE_JPEG_FORMAT) {
+                availableFormats[i] = ImageFormat.JPEG;
+            }
+        }
+        return availableFormats;
+    }
+
+    private Face[] getFaces() {
+        final int FACE_LANDMARK_SIZE = 6;
+
+        Integer faceDetectMode = getBase(CaptureResult.STATISTICS_FACE_DETECT_MODE);
+        if (faceDetectMode == null) {
+            throw new AssertionError("Expect non-null face detect mode");
+        }
+
+        if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
+            return new Face[0];
+        }
+        if (faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE &&
+                faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+            throw new AssertionError("Unknown face detect mode: " + faceDetectMode);
+        }
+
+        // Face scores and rectangles are required by SIMPLE and FULL mode.
+        byte[] faceScores = getBase(CaptureResult.STATISTICS_FACE_SCORES);
+        Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES);
+        if (faceScores == null || faceRectangles == null) {
+            throw new AssertionError("Expect face scores and rectangles to be non-null");
+        } else if (faceScores.length != faceRectangles.length) {
+            throw new AssertionError(
+                    String.format("Face score size(%d) doesn match face rectangle size(%d)!",
+                            faceScores.length, faceRectangles.length));
+        }
+
+        // Face id and landmarks are only required by FULL mode.
+        int[] faceIds = getBase(CaptureResult.STATISTICS_FACE_IDS);
+        int[] faceLandmarks = getBase(CaptureResult.STATISTICS_FACE_LANDMARKS);
+        int numFaces = faceScores.length;
+        if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+            if (faceIds == null || faceLandmarks == null) {
+                throw new AssertionError("Expect face ids and landmarks to be non-null for " +
+                        "FULL mode");
+            } else if (faceIds.length != numFaces ||
+                    faceLandmarks.length != numFaces * FACE_LANDMARK_SIZE) {
+                throw new AssertionError(
+                        String.format("Face id size(%d), or face landmark size(%d) don't match " +
+                                "face number(%d)!",
+                                faceIds.length, faceLandmarks.length * FACE_LANDMARK_SIZE,
+                                numFaces));
+            }
+        }
+
+        Face[] faces = new Face[numFaces];
+        if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE) {
+            for (int i = 0; i < numFaces; i++) {
+                faces[i] = new Face(faceRectangles[i], faceScores[i]);
+            }
+        } else {
+            // CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL
+            for (int i = 0; i < numFaces; i++) {
+                Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]);
+                Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]);
+                Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]);
+                faces[i] = new Face(faceRectangles[i], faceScores[i], faceIds[i],
+                        leftEye, rightEye, mouth);
+            }
+        }
+        return faces;
+    }
+
+    private <T> void setBase(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);
+    }
+
+    // Set the camera metadata override.
+    private <T> boolean setOverride(Key<T> key, T value) {
+        if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) {
+            return setAvailableFormats((int[]) value);
+        }
+
+        // For other keys, set() falls back to setBase().
+        return false;
+    }
+
+    private boolean setAvailableFormats(int[] value) {
+        int[] availableFormat = value;
+        if (value == null) {
+            // Let setBase() to handle the null value case.
+            return false;
+        }
+
+        int[] newValues = new int[availableFormat.length];
+        for (int i = 0; i < availableFormat.length; i++) {
+            newValues[i] = availableFormat[i];
+            if (availableFormat[i] == ImageFormat.JPEG) {
+                newValues[i] = NATIVE_JPEG_FORMAT;
+            }
+        }
+
+        setBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, newValues);
+        return true;
+    }
+
     private long mMetadataPtr; // native CameraMetadata*
 
     private native long nativeAllocate();
@@ -538,7 +680,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
      * @hide
      */
     public byte[] readValues(int tag) {
-     // TODO: Optimization. Native code returns a ByteBuffer instead.
+        // TODO: Optimization. Native code returns a ByteBuffer instead.
         return nativeReadValues(tag);
     }
 
index 874e078..3f17aa9 100644 (file)
@@ -18,9 +18,11 @@ package com.android.mediaframeworktest.unit;
 
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.graphics.ImageFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.Face;
 import android.hardware.camera2.Rational;
 import android.hardware.camera2.Size;
 import android.hardware.camera2.impl.CameraMetadataNative;
@@ -30,9 +32,6 @@ import static android.hardware.camera2.impl.CameraMetadataNative.*;
 import java.lang.reflect.Array;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.nio.IntBuffer;
-
-import static org.junit.Assert.assertArrayEquals;
 
 /**
  * <pre>
@@ -57,6 +56,7 @@ public class CameraMetadataTest extends junit.framework.TestCase {
     // Tags
     static final int ANDROID_COLOR_CORRECTION_MODE = ANDROID_COLOR_CORRECTION_START;
     static final int ANDROID_COLOR_CORRECTION_TRANSFORM = ANDROID_COLOR_CORRECTION_START + 1;
+    static final int ANDROID_COLOR_CORRECTION_GAINS = ANDROID_COLOR_CORRECTION_START + 2;
 
     static final int ANDROID_CONTROL_AE_ANTIBANDING_MODE = ANDROID_CONTROL_START;
     static final int ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION = ANDROID_CONTROL_START + 1;
@@ -131,7 +131,8 @@ public class CameraMetadataTest extends junit.framework.TestCase {
     @SmallTest
     public void testGetTypeFromTag() {
         assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE));
-        assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM));
+        assertEquals(TYPE_RATIONAL, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM));
+        assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS));
         assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE));
         assertEquals(TYPE_INT32,
                 CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION));
@@ -187,30 +188,30 @@ public class CameraMetadataTest extends junit.framework.TestCase {
         assertEquals(false, mMetadata.isEmpty());
 
         //
-        // android.colorCorrection.transform (3x3 matrix)
+        // android.colorCorrection.colorCorrectionGains (float x 4 array)
         //
 
-        final float[] transformMatrix = new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
-        byte[] transformMatrixAsByteArray = new byte[transformMatrix.length * 4];
-        ByteBuffer transformMatrixByteBuffer =
-                ByteBuffer.wrap(transformMatrixAsByteArray).order(ByteOrder.nativeOrder());
-        for (float f : transformMatrix)
-            transformMatrixByteBuffer.putFloat(f);
+        final float[] colorCorrectionGains = new float[] { 1.0f, 2.0f, 3.0f, 4.0f};
+        byte[] colorCorrectionGainsAsByteArray = new byte[colorCorrectionGains.length * 4];
+        ByteBuffer colorCorrectionGainsByteBuffer =
+                ByteBuffer.wrap(colorCorrectionGainsAsByteArray).order(ByteOrder.nativeOrder());
+        for (float f : colorCorrectionGains)
+            colorCorrectionGainsByteBuffer.putFloat(f);
 
         // Read
-        assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM));
-        mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, transformMatrixAsByteArray);
+        assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS));
+        mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, colorCorrectionGainsAsByteArray);
 
         // Write
-        assertArrayEquals(transformMatrixAsByteArray,
-                mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM));
+        assertArrayEquals(colorCorrectionGainsAsByteArray,
+                mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS));
 
         assertEquals(2, mMetadata.getEntryCount());
         assertEquals(false, mMetadata.isEmpty());
 
         // Erase
-        mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, null);
-        assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM));
+        mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, null);
+        assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS));
         assertEquals(1, mMetadata.getEntryCount());
     }
 
@@ -279,7 +280,7 @@ public class CameraMetadataTest extends junit.framework.TestCase {
     @SmallTest
     public void testReadWritePrimitiveArray() {
         // int32 (n)
-        checkKeyGetAndSetArray("android.sensor.info.availableSensitivities", int[].class,
+        checkKeyGetAndSetArray("android.sensor.info.sensitivityRange", int[].class,
                 new int[] {
                         0xC0FFEE, 0xDEADF00D
                 });
@@ -379,7 +380,9 @@ public class CameraMetadataTest extends junit.framework.TestCase {
                 new AvailableFormat[] {
                         AvailableFormat.RAW_SENSOR,
                         AvailableFormat.YV12,
-                        AvailableFormat.IMPLEMENTATION_DEFINED
+                        AvailableFormat.IMPLEMENTATION_DEFINED,
+                        AvailableFormat.YCbCr_420_888,
+                        AvailableFormat.BLOB
                 });
 
     }
@@ -431,12 +434,13 @@ public class CameraMetadataTest extends junit.framework.TestCase {
                         AvailableFormat.RAW_SENSOR,
                         AvailableFormat.YV12,
                         AvailableFormat.IMPLEMENTATION_DEFINED,
-                        AvailableFormat.YCbCr_420_888
+                        AvailableFormat.YCbCr_420_888,
+                        AvailableFormat.BLOB
                 });
 
-        Key<AeAntibandingMode> availableFormatsKey =
-                new Key<AeAntibandingMode>("android.scaler.availableFormats",
-                        AeAntibandingMode.class);
+        Key<AvailableFormat[]> availableFormatsKey =
+                new Key<AvailableFormat[]>("android.scaler.availableFormats",
+                        AvailableFormat[].class);
         byte[] availableFormatValues = mMetadata.readValues(CameraMetadataNative
                 .getTag(availableFormatsKey.getName()));
 
@@ -444,7 +448,8 @@ public class CameraMetadataTest extends junit.framework.TestCase {
                 0x20,
                 0x32315659,
                 0x22,
-                0x23
+                0x23,
+                0x21
         };
 
         ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder());
@@ -523,4 +528,115 @@ public class CameraMetadataTest extends junit.framework.TestCase {
     <T> void compareGeneric(T expected, T actual) {
         assertEquals(expected, actual);
     }
+
+    @SmallTest
+    public void testReadWriteOverride() {
+        //
+        // android.scaler.availableFormats (int x n array)
+        //
+        int[] availableFormats = new int[] {
+                0x20,       // RAW_SENSOR
+                0x32315659, // YV12
+                0x11,       // YCrCb_420_SP
+                0x100,      // ImageFormat.JPEG
+                0x22,       // IMPLEMENTATION_DEFINED
+                0x23,       // YCbCr_420_888
+        };
+        int[] expectedIntValues = new int[] {
+                0x20,       // RAW_SENSOR
+                0x32315659, // YV12
+                0x11,       // YCrCb_420_SP
+                0x21,       // BLOB
+                0x22,       // IMPLEMENTATION_DEFINED
+                0x23,       // YCbCr_420_888
+        };
+        int availableFormatTag = CameraMetadataNative.getTag("android.scaler.availableFormats");
+
+        // Write
+        mMetadata.set(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, availableFormats);
+
+        byte[] availableFormatValues = mMetadata.readValues(availableFormatTag);
+
+        ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder());
+
+        assertEquals(expectedIntValues.length * 4, availableFormatValues.length);
+        for (int i = 0; i < expectedIntValues.length; ++i) {
+            assertEquals(expectedIntValues[i], bf.getInt());
+        }
+        // Read
+        byte[] availableFormatsAsByteArray = new byte[expectedIntValues.length * 4];
+        ByteBuffer availableFormatsByteBuffer =
+                ByteBuffer.wrap(availableFormatsAsByteArray).order(ByteOrder.nativeOrder());
+        for (int value : expectedIntValues) {
+            availableFormatsByteBuffer.putInt(value);
+        }
+        mMetadata.writeValues(availableFormatTag, availableFormatsAsByteArray);
+
+        int[] resultFormats = mMetadata.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
+        assertNotNull("result available formats shouldn't be null", resultFormats);
+        assertArrayEquals(availableFormats, resultFormats);
+
+        //
+        // android.statistics.faces (Face x n array)
+        //
+        int[] expectedFaceIds = new int[] {1, 2, 3, 4, 5};
+        byte[] expectedFaceScores = new byte[] {10, 20, 30, 40, 50};
+        int numFaces = expectedFaceIds.length;
+        Rect[] expectedRects = new Rect[numFaces];
+        for (int i = 0; i < numFaces; i++) {
+            expectedRects[i] = new Rect(i*4 + 1, i * 4 + 2, i * 4 + 3, i * 4 + 4);
+        }
+        int[] expectedFaceLM = new int[] {
+                1, 2, 3, 4, 5, 6,
+                7, 8, 9, 10, 11, 12,
+                13, 14, 15, 16, 17, 18,
+                19, 20, 21, 22, 23, 24,
+                25, 26, 27, 28, 29, 30,
+        };
+        Point[] expectedFaceLMPoints = new Point[numFaces * 3];
+        for (int i = 0; i < numFaces; i++) {
+            expectedFaceLMPoints[i*3] = new Point(expectedFaceLM[i*6], expectedFaceLM[i*6+1]);
+            expectedFaceLMPoints[i*3+1] = new Point(expectedFaceLM[i*6+2], expectedFaceLM[i*6+3]);
+            expectedFaceLMPoints[i*3+2] = new Point(expectedFaceLM[i*6+4], expectedFaceLM[i*6+5]);
+        }
+
+        /**
+         * Read - FACE_DETECT_MODE == FULL
+         */
+        mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE,
+                CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL);
+        mMetadata.set(CaptureResult.STATISTICS_FACE_IDS, expectedFaceIds);
+        mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores);
+        mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects);
+        mMetadata.set(CaptureResult.STATISTICS_FACE_LANDMARKS, expectedFaceLM);
+        Face[] resultFaces = mMetadata.get(CaptureResult.STATISTICS_FACES);
+        assertEquals(numFaces, resultFaces.length);
+        for (int i = 0; i < numFaces; i++) {
+            assertEquals(expectedFaceIds[i], resultFaces[i].getId());
+            assertEquals(expectedFaceScores[i], resultFaces[i].getScore());
+            assertEquals(expectedRects[i], resultFaces[i].getBounds());
+            assertEquals(expectedFaceLMPoints[i*3], resultFaces[i].getLeftEyePosition());
+            assertEquals(expectedFaceLMPoints[i*3+1], resultFaces[i].getRightEyePosition());
+            assertEquals(expectedFaceLMPoints[i*3+2], resultFaces[i].getMouthPosition());
+        }
+
+        /**
+         * Read - FACE_DETECT_MODE == SIMPLE
+         */
+        mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE,
+                CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE);
+        mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores);
+        mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects);
+        Face[] resultSimpleFaces = mMetadata.get(CaptureResult.STATISTICS_FACES);
+        assertEquals(numFaces, resultSimpleFaces.length);
+        for (int i = 0; i < numFaces; i++) {
+            assertEquals(Face.ID_UNSUPPORTED, resultSimpleFaces[i].getId());
+            assertEquals(expectedFaceScores[i], resultSimpleFaces[i].getScore());
+            assertEquals(expectedRects[i], resultSimpleFaces[i].getBounds());
+            assertNull(resultSimpleFaces[i].getLeftEyePosition());
+            assertNull(resultSimpleFaces[i].getRightEyePosition());
+            assertNull(resultSimpleFaces[i].getMouthPosition());
+        }
+
+    }
 }