Added quality test for camera's metering function.

- The metering test contains multiple mini-tests for different
  scenarios.
- By default the tests will be executed one by one.

Change-Id: Ib912f9495c6a08a0b5fa60427ea09949eeaed2bc
diff --git a/apps/CtsVerifier/include/colorchecker/meteringtest.h b/apps/CtsVerifier/include/colorchecker/meteringtest.h
new file mode 100644
index 0000000..3115d93
--- /dev/null
+++ b/apps/CtsVerifier/include/colorchecker/meteringtest.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef METERINGTEST_H
+#define METERINGTEST_H
+
+#include <vector>
+
+#include "imagetesthandler.h"
+
+// Constructs a test handler for the metering test. The instance holds the pixel
+// values of the gray checkers on the color checker under different metering
+// settins. It can also compare two arrays of pixel values to tell whether one
+// is brighter or equivalent or darker than the other one.
+class MeteringTest : public ImageTestHandler {
+  public:
+    MeteringTest() : ImageTestHandler() {}
+    MeteringTest(const int debugHeight, const int debugWidth) :
+            ImageTestHandler(debugHeight, debugWidth) {}
+    ~MeteringTest() {}
+
+    void addDataToList(const std::vector<Vec3f>* checkerColors) {
+        mCheckerColors.push_back(*checkerColors);
+        delete checkerColors;
+    }
+
+    void processData();
+
+    void clearData();
+
+    const std::vector<bool>* getComparisonResults() const {
+        return (&mComparisonResults);
+    }
+
+  private:
+    bool isDarkerThan(const std::vector<Vec3f>* checkerColors1,
+                      const std::vector<Vec3f>* checkerColors2) const;
+    bool isEquivalentTo(const std::vector<Vec3f>* checkerColors1,
+                        const std::vector<Vec3f>* checkerColors2) const;
+
+    std::vector<std::vector<Vec3f> > mCheckerColors;
+    std::vector<bool> mComparisonResults;
+    int mNumPatches;
+};
+
+#endif
diff --git a/apps/CtsVerifier/jni/cameraanalyzer/Android.mk b/apps/CtsVerifier/jni/cameraanalyzer/Android.mk
index a0fb236..06e075e 100644
--- a/apps/CtsVerifier/jni/cameraanalyzer/Android.mk
+++ b/apps/CtsVerifier/jni/cameraanalyzer/Android.mk
@@ -25,9 +25,9 @@
 LOCAL_SRC_FILES := com_android_cts_verifier_camera_analyzer_CameraTests.cpp \
                 com_android_cts_verifier_camera_analyzer_ColorCheckerTest.cpp \
                 com_android_cts_verifier_camera_analyzer_AutoLockTest.cpp \
+                com_android_cts_verifier_camera_analyzer_MeteringTest.cpp \
                 #com_android_cts_verifier_camera_analyzer_ExposureCompensationTest.cpp \
-                com_android_cts_verifier_camera_analyzer_WhiteBalanceTest.cpp \
-                com_android_cts_verifier_camera_analyzer_MeteringTest.cpp
+                com_android_cts_verifier_camera_analyzer_WhiteBalanceTest.cpp
 
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../include/colorchecker $(JNI_H_INCLUDE)
 
diff --git a/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_MeteringTest.cpp b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_MeteringTest.cpp
new file mode 100644
index 0000000..faebe21
--- /dev/null
+++ b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_MeteringTest.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+#define LOG_NDEBUG 0
+
+#define LOG_TAG "MeteringJNI"
+#include <utils/Log.h>
+#include "com_android_cts_verifier_camera_analyzer_MeteringTest.h"
+
+#include <vector>
+#include <string>
+#include <string.h>
+#include <math.h>
+
+#include "testingimage.h"
+#include "meteringtest.h"
+#include "vec2.h"
+#include "android/bitmap.h"
+
+jlong Java_com_android_cts_verifier_camera_analyzer_MeteringTest_createMeteringTest(
+        JNIEnv*      env,
+        jobject      thiz) {
+
+    MeteringTest* testHandler = new MeteringTest();
+    long handlerAddress = (long)testHandler;
+    return handlerAddress;
+}
+
+void Java_com_android_cts_verifier_camera_analyzer_MeteringTest_createMeteringClass(
+        JNIEnv*      env,
+        jobject      thiz,
+        jlong        inputImageAddress,
+        jlong        inputHandlerAddress,
+        jlong        checkercenterAddress,
+        jlong        checkerradiusAddress){
+
+    ALOGV("JNI createMeteringClass starts!");
+    long imageAddress = (long)inputImageAddress;
+    long handlerAddress = (long)inputHandlerAddress;
+
+    TestingImage *image = (TestingImage*) imageAddress;
+    MeteringTest *testHandler = (MeteringTest*) handlerAddress;
+
+    std::vector<std::vector< Vec2f > >* checkerCenter =
+            (std::vector<std::vector< Vec2f > >*) (long) checkercenterAddress;
+    std::vector<std::vector< float > >* checkerRadius =
+            (std::vector<std::vector< float > >*) (long) checkerradiusAddress;
+    ALOGV("Classes recovered");
+
+    testHandler->addDataToList(image->getColorChecker(3, 4, 0, 6,
+                                                      checkerCenter,
+                                                      checkerRadius));
+
+    delete image;
+}
+
+void Java_com_android_cts_verifier_camera_analyzer_MeteringTest_processMeteringTest(
+        JNIEnv*          env,
+        jobject          thiz,
+        jlong            inputHandlerAddress,
+        jbooleanArray    tempArray) {
+
+    ALOGV("Processing Auto Lock data!");
+
+    long handlerAddress = (long) inputHandlerAddress;
+    MeteringTest *testHandler = (MeteringTest*) handlerAddress;
+
+    testHandler->processData();
+
+    const std::vector<bool>* nativeComparisonResults =
+            testHandler->getComparisonResults();
+    jboolean jComparisonResults[nativeComparisonResults->size()];
+
+    for (int i = 0; i < nativeComparisonResults->size(); ++i) {
+        jComparisonResults[i] = (jboolean) (*nativeComparisonResults)[i];
+    }
+
+    env->SetBooleanArrayRegion(tempArray,
+                               0, nativeComparisonResults->size(),
+                               jComparisonResults);
+    testHandler->clearData();
+}
+
+// Find the gray checker borders from the native array of checker center and
+// radius. Convert the coordinate to the coordinates accepted by Android
+// Camera.Area type, which defines the top left corner to (-1000, -1000) and
+// bottom right corner to (1000, 1000).
+void Java_com_android_cts_verifier_camera_analyzer_MeteringTest_findGreyCoordinates(
+        JNIEnv*      env,
+        jobject      thiz,
+        jintArray    greyCoordinates,
+        jlong        checkercenterAddress,
+        jlong        checkerradiusAddress){
+
+    ALOGV("Start finding grey coordinates");
+
+    std::vector<std::vector< Vec2f > >* checkerCenter =
+            (std::vector<std::vector< Vec2f > >*) (long) checkercenterAddress;
+    std::vector<std::vector< float > >* checkerRadius =
+            (std::vector<std::vector< float > >*) (long) checkerradiusAddress;
+
+    ALOGV("Checker recovered!");
+    int nativeGreyCoordinates[24];
+
+    for (int i = 0; i < 6; ++i) {
+        float radius = sqrt((*checkerRadius)[3][i]);
+        nativeGreyCoordinates[i * 4] = static_cast<int>(
+                ((*checkerCenter)[3][i].y() - radius)
+                / 160.0 * 2000.0 - 1000.0);
+        nativeGreyCoordinates[i * 4 + 1] = static_cast<int>(
+                ((*checkerCenter)[3][i].x() - radius)
+                / 120.0 * 2000.0 - 1000.0);
+        nativeGreyCoordinates[i * 4 + 2] = static_cast<int>(
+                ((*checkerCenter)[3][i].y() + radius)
+                / 160.0 * 2000.0 - 1000.0);
+        nativeGreyCoordinates[i * 4 + 3] = static_cast<int>(
+                ((*checkerCenter)[3][i].x() + radius)
+                / 120.0 * 2000.0 - 1000.0);
+
+        ALOGV("checker is bounded by %f, %f, %f",
+             (*checkerCenter)[3][i].x(), (*checkerCenter)[3][i].y(), radius);
+
+        ALOGV("Square is bounded by %d, %d, %d, %d",
+             nativeGreyCoordinates[i * 4], nativeGreyCoordinates[i * 4 + 1],
+             nativeGreyCoordinates[i * 4 + 2],
+             nativeGreyCoordinates[i * 4 + 3]);
+    }
+
+    env->SetIntArrayRegion(greyCoordinates, 0, 24, nativeGreyCoordinates);
+}
diff --git a/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_MeteringTest.h b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_MeteringTest.h
new file mode 100644
index 0000000..ecc1b96
--- /dev/null
+++ b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_MeteringTest.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef JNI_CAMERAANALYZER_METERINGTEST_H
+#define JNI_CAMERAANALYZER_METERINGTEST_H
+
+#include <jni.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jlong JNICALL
+Java_com_android_cts_verifier_camera_analyzer_MeteringTest_createMeteringTest(
+        JNIEnv*      env,
+        jobject      thiz);
+
+JNIEXPORT void JNICALL
+Java_com_android_cts_verifier_camera_analyzer_MeteringTest_createMeteringClass(
+        JNIEnv *env,
+        jobject thiz,
+        jlong inputAddress,
+        jlong handlerAddress,
+        jlong checkercenterAddress,
+        jlong checkerradiusAddress);
+
+JNIEXPORT void JNICALL
+Java_com_android_cts_verifier_camera_analyzer_MeteringTest_processMeteringTest(
+        JNIEnv*      env,
+        jobject      thiz,
+        jlong        handlerAddress,
+        jbooleanArray    tempArray);
+
+JNIEXPORT void JNICALL
+Java_com_android_cts_verifier_camera_analyzer_MeteringTest_findGreyCoordinates(
+        JNIEnv*      env,
+        jobject      thiz,
+        jintArray    greyCoordinates,
+        jlong        checkercenterAddress,
+        jlong        checkerradiusAddress);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/apps/CtsVerifier/lib/colorchecker/Android.mk b/apps/CtsVerifier/lib/colorchecker/Android.mk
index eaf1720..cdebfae 100644
--- a/apps/CtsVerifier/lib/colorchecker/Android.mk
+++ b/apps/CtsVerifier/lib/colorchecker/Android.mk
@@ -30,9 +30,9 @@
                    imagetesthandler.cpp \
                    colorcheckertest.cpp \
                    autolocktest.cpp \
-                   #exposurecompensationtest.cpp \
-                   whitebalancetest.cpp \
-                   meteringtest.cpp
+                   meteringtest.cpp \
+                   #whitebalancetest.cpp \
+                   exposurecompensationtest.cpp
 
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../include/colorchecker
 LOCAL_SHARED_LIBRARIES := libstlport \
diff --git a/apps/CtsVerifier/lib/colorchecker/meteringtest.cpp b/apps/CtsVerifier/lib/colorchecker/meteringtest.cpp
new file mode 100644
index 0000000..47de5d8
--- /dev/null
+++ b/apps/CtsVerifier/lib/colorchecker/meteringtest.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+#define LOG_NDEBUG 0
+
+#define LOG_TAG "MeteringTest"
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <cmath>
+#include <string>
+
+#include "vec2.h"
+#include "vec3.h"
+#include "meteringtest.h"
+
+const float kOverExposure = 230.f;
+const float kEqThreshold = 0.05f;
+// Processes the checker colors stored by comparing the pixel values from the
+// two scenarios in a test.
+void MeteringTest::processData() {
+    ALOGV("Start Processing Metering Test Data!");
+
+    int numTests = mCheckerColors.size() / 2;
+    mNumPatches = 0;
+
+    if (numTests > 0) {
+        mNumPatches = mCheckerColors[0].size();
+    }
+
+    for (int i = 0; i < numTests; ++i) {
+        mComparisonResults.push_back(
+                isEquivalentTo((&mCheckerColors[i * 2]),
+                               (&mCheckerColors[i * 2 + 1])));
+        mComparisonResults.push_back(
+                isDarkerThan((&mCheckerColors[i * 2]),
+                             (&mCheckerColors[i * 2 + 1])));
+    }
+}
+
+void MeteringTest::clearData() {
+    mComparisonResults.clear();
+    mCheckerColors.clear();
+}
+
+// Compares two given arrays of pixel values and decide whether the first one is
+// significantly darker than the second one.
+bool MeteringTest::isDarkerThan(
+        const std::vector<Vec3f>* checkerColors1,
+        const std::vector<Vec3f>* checkerColors2) const {
+    float meanRatio = 0.f;
+    int meanNumCount = 0;
+
+    for (int i = 0; i < mNumPatches; ++i) {
+        float luminance1 = (*checkerColors1)[i].convertToLuminance();
+        float luminance2 = (*checkerColors2)[i].convertToLuminance();
+
+        // Out of the saturation rage, define 5% as a margin for being
+        // significantly brighter.
+        if ((luminance2 < kOverExposure) && (luminance1 != 0.f)) {
+            meanRatio += luminance2 / luminance1;
+            ++meanNumCount;
+        }
+    }
+    meanRatio = meanRatio / meanNumCount;
+
+    return (meanRatio > 1 + kEqThreshold);
+}
+
+// Compares the two givn arrays of pixel values and decide whether they are
+// equivalent within an acceptable range.
+bool MeteringTest::isEquivalentTo(
+        const std::vector<Vec3f>* checkerColors1,
+        const std::vector<Vec3f>* checkerColors2) const {
+    float meanRatio = 0.f;
+    int meanNumCount = 0;
+
+    for (int i = 0; i < mNumPatches; ++i) {
+        float luminance1 = (*checkerColors1)[i].convertToLuminance();
+        float luminance2 = (*checkerColors2)[i].convertToLuminance();
+        ALOGV("Luma_1 and Luma_2 is %f, %f", luminance1, luminance2);
+
+        if ((luminance1 < kOverExposure) && (luminance2 < kOverExposure)) {
+              meanRatio += luminance2 / luminance1;
+              ++meanNumCount;
+        }
+    }
+    meanRatio = meanRatio / meanNumCount;
+
+    return ((meanRatio >= 1 - kEqThreshold) && (meanRatio <= 1 + kEqThreshold));
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java
index 30a13d9..aa12476 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java
@@ -210,7 +210,7 @@
         ColorCheckerTest.getSingletonTest().updateCamera();
         //WhiteBalanceTest.getSingletonTest().updateCamera();
         //ExposureCompensationTest.getSingletonTest().updateCamera();
-        //MeteringTest.getSingletonTest().updateCamera();
+        MeteringTest.getSingletonTest().updateCamera();
         AutoLockTest.getSingletonTest().updateCamera();
     }
 
@@ -358,14 +358,14 @@
         public void onClick(View v) {
             Log.v(TAG, "Running new metering tests!");
 
-            /*MeteringTest meteringTest = MeteringTest.getSingletonTest();
+            MeteringTest meteringTest = MeteringTest.getSingletonTest();
 
             mCurrentTest = meteringTest;
-            initializeAdapter();*/
+            initializeAdapter();
 
             // Loads the memory address of the checker centers and radius
             // from the this class and set the two values for the new test.
-            //MeteringTest.setCheckerAddress(mCheckerCenterAddress, mCheckerRadiusAddress);
+            MeteringTest.setCheckerAddress(mCheckerCenterAddress, mCheckerRadiusAddress);
         }
     };
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/MeteringTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/MeteringTest.java
new file mode 100644
index 0000000..ff5067a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/MeteringTest.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2011 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 com.android.cts.verifier.camera.analyzer;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.hardware.Camera.Area;
+import android.util.Log;
+import android.widget.ImageView;
+
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Implements a test to verify whether the camera metering system works as
+ * described in the API.
+ *
+ * The test consists two sub-categories. The first one has tests with only
+ * one metering area defined. The second one has tests with two metering areas
+ * defined. For each single sub-test, we use a random number generator to
+ * decide where to put some of the metering areas to and how much weight should
+ * be assigned to each area. For different tests, we use different ways to
+ * define other metering areas and their weight, in order to cover all possible
+ * fail cases. The metering areas are contrained to the grey squares in the
+ * bottom of the color checker.
+ */
+public class MeteringTest extends CameraTests {
+
+    private static final String TAG = "MeteringTest";
+
+    /** A long wait.*/
+    private static final int LONG_SLEEP = 4000;
+    /** Debug result in text. */
+    private String mDebugText;
+    /** Thread lock. */
+    private final Object mProcessingImage = new Object();
+    /** Memory address of the native test handler. */
+    private long mTestHandler;
+    /** The maximum number of metering area the device supports. */
+    private int mMaxNumMeteringArea;
+    /** The metering areas. */
+    private List<Camera.Area> mGreyAreas;
+    /** The coordinates of the grey squares on the color checker. */
+    private int[] mGreyCoordinates = new int[24];
+    /** Random number generator. */
+    private final Random mRandomGenerator = new Random();
+    /** Reference comparison result for tests. */
+    private ArrayList<Boolean>  mReferenceCompareResults;
+    /** Number of tests in the same instance. */
+    private int mTestCount;
+    /** Reference test logs. */
+    private ArrayList<String> mReferenceLogs;
+    /** Test result to show. */
+    private String[] mTestResults;
+    /** Number of tests. */
+    private int mNumTests;
+    /** Camera Parameters. */
+    private Camera.Parameters mParams;
+    /** Singleton test instance. */
+    private static MeteringTest singletonTest = null;
+
+    /** Constructs a <code>MeteringTest</code> instance with the given
+     * camera pointer.
+     */
+    private MeteringTest() {
+        super();
+    }
+
+    public void updateCamera() {
+        // Looks up how many metering area the device supports.
+        mParams = mTestCamera.getParameters();
+        mMaxNumMeteringArea = mParams.getMaxNumMeteringAreas();
+        Log.v(TAG, String.format("Maximum number if metering area is %d", mMaxNumMeteringArea));
+        if (mMaxNumMeteringArea == 0) {
+            mDebugText = "Custom Metering not supported!";
+            Log.v(TAG, "Custom Metering not supported");
+        }
+    }
+
+    public static synchronized MeteringTest getSingletonTest() {
+        if (singletonTest == null) {
+            Log.v(TAG, "Creating a new MeteringTest instance");
+            singletonTest = new MeteringTest();
+            singletonTest.initializeTest();
+        }
+        return singletonTest;
+    }
+
+    private void initializeTest() {
+        // Creates a native metering test handler.
+        mTestHandler = createMeteringTest();
+        mDebugText = new String();
+        mReferenceCompareResults = new ArrayList<Boolean>();
+        mReferenceLogs = new ArrayList<String>();
+        mNumTests = 3;
+        mTestResults = new String[mNumTests];
+        for (int i = 0; i < mNumTests; ++i) {
+            mTestResults[i] = "...";
+        }
+    }
+
+    /**
+     * Runs the metering test instance.
+     */
+    @Override
+    public synchronized void run(int index) {
+        if (index == 0) {
+            run(1);
+            run(2);
+            return;
+        }
+        Log.v(TAG, "MeteringTest thread started!");
+
+        // Finds the coordinates of the grey squares on the color checker.
+        // The coordinate system has (-1000, -1000) on the upper left corner.
+        // And (1000, 1000) on the bottom right corner.
+        findGreyCoordinates(mGreyCoordinates, getCheckerCenter(), getCheckerRadius());
+
+        if (mMaxNumMeteringArea > 0) {
+            mTestCount = 0;
+            // Runs the metering tests category by category.
+            switch (index) {
+                case 1:
+                    runOneAreaTest();
+                    break;
+                case 2:
+                    if (mMaxNumMeteringArea > 1) {
+                        runTwoAreasTest();
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        mParams = mTestCamera.getParameters();
+        mParams.setMeteringAreas(null);
+        mTestCamera.setParameters(mParams);
+
+        boolean[] testCompareResults = new boolean[2 * mTestCount];
+
+        // Processes the image data taken so far and stores the test results.
+        processMeteringTest(mTestHandler, testCompareResults);
+        // Prepares debug output based on the test results.
+        prepareDebugText(testCompareResults, index);
+
+        mReferenceCompareResults.clear();
+        mReferenceLogs.clear();
+    }
+
+    /**
+     * Prepares the test results in HTML text string to show in the UI.
+     *
+     * If the test result is the same as the reference result, the text will be
+     * shown in green. Otherwise it would be shown as red.
+     *
+     * @param testCompareResults the array storing the comparison results from
+     * the data taken by the camera so far.
+     */
+    private void prepareDebugText(boolean[] testCompareResults, int index) {
+        mDebugText = "";
+        boolean groupTestPassed = true;
+        for (int i = 0; i < mTestCount; ++i) {
+              String testLog;
+              boolean testPassed = true;
+              testLog = mReferenceLogs.get(i);
+              mDebugText += (testLog + "<br/>");
+
+              if (testCompareResults[i * 2] == mReferenceCompareResults.get(i * 2)) {
+                  mDebugText += String.format(
+                      "Picture 1 equivalent to Picture 2 is %b \n",
+                      testCompareResults[i * 2]);
+              } else {
+                  mDebugText += String.format(
+                      "Picture 1 equivalent to Picture 2 is %b \n",
+                      testCompareResults[i * 2]);
+                  testPassed = false;
+              }
+
+              if (testCompareResults[i * 2 + 1] == mReferenceCompareResults.get(i * 2 + 1)) {
+                  mDebugText += String.format(
+                      "Picture 1 darker than Picture 2 is %b \n",
+                      testCompareResults[i * 2 + 1]);
+              } else {
+                  mDebugText += String.format(
+                      "Picture 1 darker than Picture 2 is %b \n",
+                      testCompareResults[i * 2 + 1]);
+                  testPassed = false;
+              }
+
+              if (testPassed) {
+                  mDebugText += "Test passed! \n";
+              } else {
+                  mDebugText += "Test failed! \n";
+                  groupTestPassed = false;
+              }
+              Log.v(TAG, String.format("%s", mDebugText));
+         }
+
+        if (groupTestPassed) {
+            mTestResults[index] = "Passed";
+        } else {
+            mTestResults[index] = "Failed";
+        }
+
+    }
+
+    /**
+     * Runs tests to check whether the metering functionalities work properly
+     * when one metering area is added.
+     */
+    private void runOneAreaTest() {
+        int weight1;
+        int weight2;
+        int square1;
+        int square2;
+
+        Log.v(TAG, "Running one area Test");
+
+        // Test case 1: Two images have the same metering area. Image 1 has
+        // a diffent weight than Image 2. The result images should be
+        // identical.
+        // Tests whether weight normalization works.
+        square1 = mRandomGenerator.nextInt(6);
+        weight1 = mRandomGenerator.nextInt(100) + 1;
+        runSingleTest(square1, square1, weight1);
+
+        square2 = square1;
+        weight2 = mRandomGenerator.nextInt(100) + 901;
+        runSingleTest(square2, square2, weight2);
+        mReferenceCompareResults.add(true);
+        mReferenceCompareResults.add(false);
+        Log.v(TAG, String.format("Running test for %d square with weights %d, %d",
+                                 square1, weight1, weight2));
+        mReferenceLogs.add(String.format(
+            "Running test for %d 1x1 square with weights %d, %d", square1, weight1, weight2));
+        ++mTestCount;
+
+        // Test case 2: Two images have different metering areas. Image 1 has
+        // one of the grey squares as its metering area. Image 2 has a darker
+        // grey square as its metering area. The weights for both images are
+        // the same. Image 1 is expected to be darker than Image 2.
+        // Tests whether metering on uni-brightness patches work.
+        square1 = mRandomGenerator.nextInt(5);
+        weight1 = mRandomGenerator.nextInt(1000) + 1;
+        runSingleTest(square1, square1, weight1);
+
+        square2 = mRandomGenerator.nextInt(6 - square1 - 1) + square1 + 1;
+        weight2 = weight1;
+        runSingleTest(square2, square2, weight2);
+        mReferenceCompareResults.add(false);
+        mReferenceCompareResults.add(true);
+        mReferenceLogs.add(String.format(
+            "Running test for %d, %d 1x1 square with weight %d", square1, square2, weight1));
+        ++mTestCount;
+
+        // Test case 3: Two images have different metering areas. Image one has
+        // one of the grey squares as its metering area. Image 2 has a
+        // rectangle which contains Image 1's metering area and the neighboring
+        // darker grey square. The weights for both tests are the same. Image 1
+        // is expected to be darker than Image 2.
+        // Tests whether metering on patches with different brightness works.
+        square1 = mRandomGenerator.nextInt(5);
+        weight1 = mRandomGenerator.nextInt(1000) + 1;
+        runSingleTest(square1, square1, weight1);
+
+        square2 = square1;
+        weight2 = weight1;
+        runSingleTest(square2, square2 + 1, weight2);
+        mReferenceCompareResults.add(false);
+        mReferenceCompareResults.add(true);
+        mReferenceLogs.add(String.format(
+            "Running test for %d 1x1, 1x2 square with weight %d", square1, weight1));
+        ++mTestCount;
+
+        // Test case 4: Two images have different metering areas. Image one has
+        // two neighboring grey squares as its metering area. Image 2 has two
+        // darker neighboring grey squares as its metering area. Weights are
+        // the same for both images. Image 1 is expected to be darker than
+        // Image 2.
+        // Tests whether metering on two mixed-brightness patches work.
+        square1 = mRandomGenerator.nextInt(4);
+        weight1 = mRandomGenerator.nextInt(1000) + 1;
+        runSingleTest(square1, square1 + 1, weight1);
+
+        square2 = mRandomGenerator.nextInt(5 - square1 - 1) + square1 + 1;
+        weight2 = weight1;
+        runSingleTest(square2, square2 + 1, weight2);
+        mReferenceCompareResults.add(false);
+        mReferenceCompareResults.add(true);
+        mReferenceLogs.add(String.format(
+            "Running test for %d, %d 1x2 square with weight %d", square1, square2, weight1));
+        ++mTestCount;
+
+        // Test case 5: Two images have different metering areas. Image one has
+        // three neighboring grey squares as its metering area. Image 2 has
+        // three darker neighboring grey squares as its metering area. Weights
+        // are the same. Image 1 is expected to be darker than Image 2.
+        // Tests whether metering on three mixed-brightness patches work.
+        square1 = mRandomGenerator.nextInt(3);
+        weight1 = mRandomGenerator.nextInt(1000) + 1;
+        runSingleTest(square1, square1 + 2, weight1);
+
+        square2 = mRandomGenerator.nextInt(4 - square1 - 1) + square1 + 1;
+        weight2 = weight1;
+        runSingleTest(square2, square2 + 2, weight2);
+        mReferenceCompareResults.add(false);
+        mReferenceCompareResults.add(true);
+        mReferenceLogs.add(String.format(
+            "Running test for %d, %d 1x3 square with weight %d", square1, square2, weight1));
+        ++mTestCount;
+    }
+
+    /**
+     * Runs metering tests to verify the functionalities when there are two
+     * areas set as the metering area.
+     */
+    private void runTwoAreasTest() {
+        int[] weight1 = new int[2];
+        int[] weight2 = new int[2];
+        int[] square1Start = new int[2];
+        int[] square2Start = new int[2];
+        int[] square1End = new int[2];
+        int[] square2End = new int[2];
+
+        Log.v(TAG, "Running two-area Test");
+
+        // Test case 1: Image 1 has two metering areas. They are two adjacent
+        // grey squares (each set as a metering area). The two areas have the
+        // same weight. Image 2 has one metering area, which is the combination
+        // of Image 1's two metering areas as a rectangle. The weight is the
+        // same as that of Image 1's individual area. Image 1 is expected to
+        // be equivalent to Image 2.
+        // Tests whether having seperating a metering area into two will yield
+        // the same result.
+        square1Start[0] = mRandomGenerator.nextInt(5);
+        square1End[0] = square1Start[0];
+        weight1[0] = mRandomGenerator.nextInt(1000) + 1;
+        square1Start[1] = square1Start[0] + 1;
+        square1End[1] = square1Start[1];
+        weight1[1] = weight1[0];
+        runMultipleAreaTest(square1Start, square1End, weight1);
+
+        square2Start[0] = square1Start[0];
+        weight2[0] = weight1[0];
+        runSingleTest(square2Start[0], square2Start[0] + 1, weight2[0]);
+        mReferenceCompareResults.add(true);
+        mReferenceCompareResults.add(false);
+        mReferenceLogs.add(String.format(
+            "Running test for %d, %d 1x1 square with weight %d",
+            square1Start[0], square1Start[1], weight1[0]));
+        ++mTestCount;
+
+        // Test case 2: Image 1 has two metering areas. They are two random
+        // grey squareson the color checker. The brighter square has a larger
+        // weight than the darker square. Image 2 has the same two metering
+        // areas as Image 1. The weights for both are equal to the weight of
+        // the darker square in Image 1, which is smaller than the weight of
+        // the brighter square in Image 1. Image 1 is expected to be darker
+        // than Image 2.
+        // Tests whether giving one of the two metering areas a different
+        // weight would change the image in the correct way.
+        square1Start[0] = mRandomGenerator.nextInt(4);
+        square1End[0] = square1Start[0];
+        weight1[0] = mRandomGenerator.nextInt(100) + 901;
+        square1Start[1] = mRandomGenerator.nextInt(5 - square1Start[0] - 1) + square1Start[0] + 1;
+        square1End[1] = square1Start[1];
+        weight1[1] = mRandomGenerator.nextInt(100) + 1;
+        runMultipleAreaTest(square1Start, square1End, weight1);
+
+        square2Start[0] = square1Start[0];
+        square2End[0] = square2Start[0];
+        weight2[0] = weight1[1];
+        square2Start[1] = square1Start[1];
+        square2End[1] = square1End[1];
+        weight2[1] = weight2[0];
+        runMultipleAreaTest(square2Start, square2End, weight2);
+        mReferenceCompareResults.add(false);
+        mReferenceCompareResults.add(true);
+        mReferenceLogs.add(String.format(
+            "Running test for %d, %d 1x1 square with weight %d, %d",
+            square1Start[0], square1Start[1], weight1[0], weight2[1]));
+        ++mTestCount;
+
+        // Test case 3: Image 1 has two metering areas. Both are set to the
+        // same random grey square on the color checker. The weight for both
+        // are the same. Image 2 has one meterig area, which is the same as
+        // Image 1's chosen grey square. The weight for it is the same as
+        // Image 1's weight for one metering area. Image 1 is expected to be
+        // equivalent to Image 2.
+        // Tests whether defining overlapping metering area works.
+        square1Start[0] = mRandomGenerator.nextInt(6);
+        square1End[0] = square1Start[0];
+        weight1[0] = mRandomGenerator.nextInt(1000) + 1;
+        square1Start[1] = square1Start[0];
+        square1End[1] = square1Start[1];
+        weight1[1] = weight1[0];
+        runMultipleAreaTest(square1Start, square1End, weight1);
+
+        square2Start[0] = square1Start[0];
+        square2End[0] = square2Start[0];
+        weight2[0] = weight1[0];
+        runSingleTest(square2Start[0], square2End[0], weight2[0]);
+        mReferenceCompareResults.add(true);
+        mReferenceCompareResults.add(false);
+        mReferenceLogs.add(String.format(
+            "Running test for %d 1x1 square with weight %d,", square1Start[0], weight1[0]));
+        ++mTestCount;
+
+        // Test case 4: Image 1 has two metering areas. The first one is a
+        // grey square on the color checker. The second one is a rectangle
+        // containing the first metering area's grey square and its neighboring
+        // darker square. The weights for both metering area are the same.
+        // Image 2 has two metering areas. The first one is the same grey
+        // square as Image 1's first metering area. The second one is the
+        // neighboring darker grey square. The weight for the brighter square
+        // is double the weight of Image 1's weights for each metering area.
+        // The weight for the Image 2's darker grey square is the same as
+        // Image 1's weight for each of its metering areas. Image 1 is expected
+        // to be equivalent to Image 2.
+        // Tests whether the weights for overlapping metering area add up.
+        square1Start[0] = mRandomGenerator.nextInt(2);
+        square1End[0] = square1Start[0];
+        weight1[0] = mRandomGenerator.nextInt(500) + 1;
+        square1Start[1] = square1Start[0];
+        square1End[1] = square1Start[1] + 1;
+        weight1[1] = weight1[0];
+        runMultipleAreaTest(square1Start, square1End, weight1);
+
+        square2Start[0] = square1Start[0];
+        square2End[0] = square1End[0];
+        weight2[0] = weight1[0] * 2;
+        square2Start[1] = square2Start[0] + 1;
+        square2End[1] = square2Start[1];
+        weight2[1] = weight1[1];
+        runMultipleAreaTest(square2Start, square2End, weight2);
+        mReferenceCompareResults.add(true);
+        mReferenceCompareResults.add(false);
+        mReferenceLogs.add(String.format(
+            "Running test for %d 1x2 1x1 and 1x2 square with weight %d,",
+            square1Start[0], weight1[0]));
+        ++mTestCount;
+    }
+
+    /**
+     * Runs the metering test when multiple metering areas are defined.
+     *
+     * @param startIndex the array storing the index of the grey square where
+     * one metering area starts
+     * @param endIndex the array storing the index of the grey square where one
+     * metering area ends.
+     * @param weight the array storing the weight for each metering area.
+     */
+    private void runMultipleAreaTest(int[] startIndex, int[] endIndex, int[] weight) {
+        int numAreas = startIndex.length;
+        mParams = mTestCamera.getParameters();
+        List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
+
+        for (int i = 0; i < numAreas; ++i) {
+            meteringAreas.add(makeArea(startIndex[i], endIndex[i], weight[i]));
+            Log.v(TAG, String.format("Add metering area for %d, %d, %d",
+                                     startIndex[i], endIndex[i], weight[i]));
+        }
+        mParams.setMeteringAreas(meteringAreas);
+        mTestCamera.setParameters(mParams);
+        takePicture();
+    }
+
+    /**
+     * Runs the metering test when one metering area is defined.
+     *
+     * @param startIndex the index of the grey square where the metering area
+     * starts
+     * @param endIndex the index of the grey square where the metering area
+     * ends.
+     * @param weight the weight for the metering area.
+     */
+    private void runSingleTest(int startIndex, int endIndex, int weight) {
+        mParams = mTestCamera.getParameters();
+        List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
+
+        Log.v(TAG, String.format("Single test for %d, %d, %d", startIndex, endIndex, weight));
+        meteringAreas.add(makeArea(startIndex, endIndex, weight));
+        mParams.setMeteringAreas(meteringAreas);
+        mTestCamera.setParameters(mParams);
+        takePicture();
+    }
+
+    /**
+     * Takes picture with the camera instance linked to this test class.
+     */
+    private void takePicture() {
+        // Waits for the metering to be stable
+        try{
+            Log.v(TAG, "Waiting for metering");
+            Thread.sleep(LONG_SLEEP);
+            Log.v(TAG, "END Waiting");
+        } catch (InterruptedException e) {}
+
+        mTestCamera.takePicture(null, null, null, mTestJpegListener);
+
+        // Locks thread until picture is taken and ready for processing.
+        synchronized (mProcessingImage) {
+            try{
+                Log.v(TAG, "Start waiting for Image");
+
+                mProcessingImage.wait();
+            } catch (InterruptedException e) {
+                Log.v(TAG, "Callback wait fails!");
+            }
+        }
+    }
+
+    /**
+     * Constructs a <code>Camera.Area</code> object of the metering area.
+     * Given the start and end index of one metering area, it takes the upper
+     * left corner of the starting square and the bottom right corner of the
+     * end square to construct an Area.
+     *
+     * @param startIndex the index of the grey square where the metering area
+     * starts
+     * @param endIndex the index of the grey square where the metering area
+     * ends
+     * @param weight the weight of this metering area.
+     *
+     * @return a <code>Camera.Area</code> object which represents this metering
+     * area
+     */
+    private Camera.Area makeArea(int startIndex, int endIndex, int weight) {
+        Rect areaRect = new Rect(mGreyCoordinates[startIndex * 4],
+                                 mGreyCoordinates[startIndex * 4 + 1],
+                                 mGreyCoordinates[endIndex * 4 + 2],
+                                 mGreyCoordinates[endIndex * 4 + 3]);
+        Camera.Area area = new Camera.Area(areaRect, weight);
+
+        return area;
+    }
+
+    @Override
+    public String getDebugText() {
+        return mDebugText;
+    }
+
+    @Override
+    public String getResultText() {
+        return mDebugText;
+    }
+
+    @Override
+    public String getTestName() {
+        return "Metering Test: \n";
+    }
+
+    @Override
+    public String getTestName(int index) {
+        switch (index) {
+            case 0:
+                return "Run all tests";
+            case 1:
+                return "One metering area tests";
+            case 2:
+                return "Multiple metering areas tests";
+            default:
+                return "";
+        }
+    }
+
+    @Override
+    public String getResult(int index) {
+        return mTestResults[index];
+    }
+
+    @Override
+    public int getNumTests() {
+        return mNumTests;
+    }
+
+    private Camera.PictureCallback mTestJpegListener = new Camera.PictureCallback() {
+        public void onPictureTaken(byte[] data, Camera mCamera) {
+            Log.v(TAG, "Shutter pressed down!");
+            Bitmap inputImage;
+            try {
+                FileOutputStream outStream = new FileOutputStream(
+                    String.format("/sdcard/metering%d.jpg", System.currentTimeMillis()));
+                outStream.write(data);
+                outStream.close();
+            } catch (FileNotFoundException e) {
+            } catch (IOException e) {}
+
+            // Decodes the input data of the camera.
+            inputImage = BitmapFactory.decodeByteArray(data, 0, data.length);
+
+            // Records the memory address of the native image class instance.
+            long bufferAddress = findNative(inputImage);
+            Log.v(TAG, "findNative method finishes");
+
+            // Cleans up the memory taken by the bitmap.
+            inputImage.recycle();
+            data = null;
+            inputImage = null;
+            System.gc();
+
+            // Add the image data to the native test handler.
+            createMeteringClass(bufferAddress, mTestHandler,
+                                getCheckerCenter(), getCheckerRadius());
+            mCamera.startPreview();
+
+            // Releases thread lock after the image is processed.
+            synchronized (mProcessingImage) {
+                mProcessingImage.notifyAll();
+            }
+        }
+    };
+
+    /**
+     * Finds the coordinates of the grey squares on the color checker.
+     * The coordinates are computed by using the checker center and radius.
+     * The coordinates are converted to a system with (-1000, -1000) in the
+     * upper left corner and (1000, 1000) in the  bottom right corner.
+     *
+     * @param greyCoordinates the array to store the coordinates of the grey
+     * squares
+     * @param checkerCenterAddress the memory address pointing to the vector
+     * storing the color checker's centers.
+     * @param checkerRadiusAddress the memory address pointing to the vetor
+     * storing the color checker's radius.
+     */
+    private native void findGreyCoordinates(int[] greyCoordinates,
+                                            long checkerCenterAddress, long checkerRadiusAddress);
+
+    /**
+     * Creates the native metering test handler with no debug image.
+     *
+     * @return the memory address pointing to the native test handler instance
+     */
+    private native long createMeteringTest();
+
+    /**
+     * Adds the data from the native image class instance to the native test
+     * handler.
+     *
+     * @param bufferAddress the meory address of the native image class
+     * @param handlerAddress the memory address of the native test handler
+     * @param checkerCenterAddress the memory address of the checker cneters
+     * @param checkerRadiusAddress the meory address of the checker radius
+     */
+    private native void createMeteringClass(long bufferAddress, long handlerAddress,
+                                            long checkerCenterAddress,
+                                            long checkerRadiusAddress);
+
+    /**
+     * Process the data stored in the native test handler and stores the
+     * comparison results.
+     *
+     * @param handlerAddress the memory address of the native test handler
+     * @param testCompareResults the boolean array to store the test comparison
+     * results
+     */
+    private native void processMeteringTest(long handlerAddress, boolean[] testCompareResults);
+
+    static {
+        System.loadLibrary("cameraanalyzer");
+    }
+}