am 4c4f07d9: Camera2: add static metadata collection
* commit '4c4f07d9740cf69e37445c23c800952f5ea72872':
Camera2: add static metadata collection
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
new file mode 100644
index 0000000..283f09b
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import android.content.pm.PackageManager;
+import android.cts.util.DeviceReportLog;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.cts.helpers.CameraMetadataGetter;
+import android.util.Log;
+
+import com.android.cts.util.ResultType;
+import com.android.cts.util.ResultUnit;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+
+/**
+ * This test collects camera2 API static metadata and reports to device report.
+ *
+ */
+public class StaticMetadataCollectionTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "StaticMetadataCollectionTest";
+
+ private DeviceReportLog mReportLog;
+
+ @Override
+ protected void setUp() throws Exception {
+ mReportLog = new DeviceReportLog();
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Deliver the report to host will automatically clear the report log.
+ mReportLog.deliverReportToHost(getInstrumentation());
+ super.tearDown();
+ }
+
+ public void testDataCollection() {
+ if (hasCameraFeature()) {
+ CameraMetadataGetter cameraInfoGetter = new CameraMetadataGetter(mCameraManager);
+ for (String id : mCameraIds) {
+ // Gather camera info
+ JSONObject cameraInfo = cameraInfoGetter.getCameraInfo(id);
+ dumpJsonObjectAsCtsResult(String.format("camera2_id%s_static_info", id), cameraInfo);
+ dumpDoubleAsCtsResult(String.format("camera2_id%s_static_info:", id)
+ + cameraInfo.toString(), 0);
+
+ JSONObject[] templates = cameraInfoGetter.getCaptureRequestTemplates(id);
+ for (int i = 0; i < templates.length; i++) {
+ dumpJsonObjectAsCtsResult(String.format("camera2_id%s_capture_template%d",
+ id, CameraMetadataGetter.TEMPLATE_IDS[i]), templates[i]);
+ if (templates[i] != null) {
+ dumpDoubleAsCtsResult(String.format("camera2_id%s_capture_template%d:",
+ id, CameraMetadataGetter.TEMPLATE_IDS[i])
+ + templates[i].toString(), 0);
+ }
+ }
+ }
+
+ try {
+ cameraInfoGetter.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to close camera info getter " + e.getMessage());
+ }
+
+ mReportLog.printSummary("Camera data collection for static info and capture request"
+ + " templates",
+ 0.0, ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ }
+
+ private void dumpDoubleAsCtsResult(String name, double value) {
+ mReportLog.printValue(name, value, ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ public void dumpDoubleArrayAsCtsResult(String name, double[] values) {
+ mReportLog.printArray(name, values, ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ private double getJsonValueAsDouble(String name, Object obj) throws Exception {
+ if (obj == null) {
+ Log.e(TAG, "Null value: " + name);
+ throw new Exception();
+ } else if (obj instanceof Double) {
+ return ((Double)obj).doubleValue();
+ } else if (obj instanceof Float) {
+ return ((Float)obj).floatValue();
+ } else if (obj instanceof Long) {
+ return ((Long)obj).longValue();
+ } else if (obj instanceof Integer) {
+ return ((Integer)obj).intValue();
+ } else if (obj instanceof Byte) {
+ return ((Byte)obj).intValue();
+ } else if (obj instanceof Short) {
+ return ((Short)obj).intValue();
+ } else if (obj instanceof Boolean) {
+ return ((Boolean)obj) ? 1 : 0;
+ } else {
+ Log.e(TAG, "Unsupported value type: " + name);
+ throw new Exception();
+ }
+ }
+
+ private void dumpJsonArrayAsCtsResult(String name, JSONArray arr) throws Exception {
+ if (arr == null || arr.length() == 0) {
+ dumpDoubleAsCtsResult(name + "[]", 0);
+ } else if (arr.get(0) instanceof JSONObject) {
+ for (int i = 0; i < arr.length(); i++) {
+ dumpJsonObjectAsCtsResult(name+String.format("[%04d]",i),(JSONObject)arr.get(i));
+ }
+ } else if (arr.get(0) instanceof JSONArray) {
+ for (int i = 0; i < arr.length(); i++) {
+ dumpJsonArrayAsCtsResult(name+String.format("[%04d]",i),(JSONArray)arr.get(i));
+ }
+ } else if (!(arr.get(0) instanceof String)) {
+ double[] values = new double[arr.length()];
+ for (int i = 0; i < arr.length(); i++) {
+ values[i] = getJsonValueAsDouble(name + "[]", arr.get(i));
+ }
+ dumpDoubleArrayAsCtsResult(name + "[]", values);
+ } else if (arr.get(0) instanceof String) {
+ for (int i = 0; i < arr.length(); i++) {
+ dumpDoubleAsCtsResult(
+ name+String.format("[%04d]",i)+" = "+(String)arr.get(i), 0);
+ }
+ } else {
+ Log.e(TAG, "Unsupported array value type: " + name);
+ throw new Exception();
+ }
+ }
+
+ private void dumpJsonObjectAsCtsResult(String name, JSONObject obj) {
+ if (obj == null) {
+ dumpDoubleAsCtsResult(name + "{}", 0);
+ return;
+ }
+ Iterator<?> keys = obj.keys();
+ while (keys.hasNext()) {
+ try {
+ String key = (String)keys.next();
+ if (obj.get(key) instanceof JSONObject) {
+ dumpJsonObjectAsCtsResult(name+"."+key, (JSONObject)obj.get(key));
+ } else if (obj.get(key) instanceof JSONArray) {
+ dumpJsonArrayAsCtsResult(name+"."+key, (JSONArray)obj.get(key));
+ } else if (!(obj.get(key) instanceof String)) {
+ dumpDoubleAsCtsResult(name+"."+key,
+ getJsonValueAsDouble(name+"."+key, obj.get(key)));
+ } else if (obj.get(key) instanceof String) {
+ dumpDoubleAsCtsResult(name+"."+key + " = " + (String)obj.get(key), 0);
+ } else {
+ Log.e(TAG, "Unsupported object field type: " + name + "." + key);
+ }
+ } catch (Exception e) {
+ // Swallow
+ }
+ }
+ }
+
+ private boolean hasCameraFeature() {
+ PackageManager packageManager = getActivity().getPackageManager();
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
new file mode 100755
index 0000000..db75cdd
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.helpers;
+
+import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.TonemapCurve;
+import android.location.Location;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Rational;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.Range;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Utility class to dump the camera metadata.
+ */
+public final class CameraMetadataGetter implements AutoCloseable {
+ private static final String TAG = CameraMetadataGetter.class.getSimpleName();
+ private static final int CAMERA_CLOSE_TIMEOUT_MS = 5000;
+ public static final int[] TEMPLATE_IDS = {
+ CameraDevice.TEMPLATE_PREVIEW,
+ CameraDevice.TEMPLATE_STILL_CAPTURE,
+ CameraDevice.TEMPLATE_RECORD,
+ CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
+ CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
+ CameraDevice.TEMPLATE_MANUAL,
+ };
+ private CameraManager mCameraManager;
+ private BlockingStateCallback mCameraListener;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
+ private static class MetadataEntry {
+ public MetadataEntry(String k, Object v) {
+ key = k;
+ value = v;
+ }
+
+ public String key;
+ public Object value;
+ }
+
+ public CameraMetadataGetter(CameraManager cameraManager) {
+ if (cameraManager == null) {
+ throw new IllegalArgumentException("can not create an CameraMetadataGetter object"
+ + " with null CameraManager");
+ }
+
+ mCameraManager = cameraManager;
+
+ mCameraListener = new BlockingStateCallback();
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ public String getCameraInfo() {
+ StringBuffer cameraInfo = new StringBuffer("{\"CameraStaticMetadata\":{");
+ CameraCharacteristics staticMetadata;
+ String[] cameraIds;
+ try {
+ cameraIds = mCameraManager.getCameraIdList();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+ return "";
+ }
+ for (String id : cameraIds) {
+ String value = null;
+ try {
+ staticMetadata = mCameraManager.getCameraCharacteristics(id);
+ value = serialize(staticMetadata).toString();
+ } catch (CameraAccessException e) {
+ Log.e(TAG,
+ "Unable to get camera camera static info, skip this camera, error: "
+ + e.getMessage());
+ }
+ cameraInfo.append("\"camera" + id + "\":"); // Key
+ cameraInfo.append(value); // Value
+ // If not last, print "," // Separator
+ if (!id.equals(cameraIds[cameraIds.length - 1])) {
+ cameraInfo.append(",");
+ }
+ }
+ cameraInfo.append("}}");
+
+ return cameraInfo.toString();
+ }
+
+ public JSONObject getCameraInfo(String cameraId) {
+ JSONObject staticMetadata = null;
+ try {
+ staticMetadata = serialize(mCameraManager.getCameraCharacteristics(cameraId));
+ } catch (CameraAccessException e) {
+ Log.e(TAG,
+ "Unable to get camera camera static info, skip this camera, error: "
+ + e.getMessage());
+ }
+ return staticMetadata;
+ }
+
+ public JSONObject[] getCaptureRequestTemplates(String cameraId) {
+ JSONObject[] templates = new JSONObject[TEMPLATE_IDS.length];
+ CameraDevice camera = null;
+ try {
+ camera = (new BlockingCameraManager(mCameraManager)).openCamera(cameraId,
+ mCameraListener, mHandler);
+ for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+ CaptureRequest.Builder request;
+ try {
+ request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+ templates[i] = serialize(request.build());
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+ + " because of error " + e.getMessage());
+ templates[i] = null;
+ }
+ }
+ return templates;
+ } catch (CameraAccessException | BlockingOpenException e) {
+ Log.e(TAG, "Unable to open camera " + cameraId + " because of error "
+ + e.getMessage());
+ return new JSONObject[0];
+ } finally {
+ if (camera != null) {
+ camera.close();
+ }
+ }
+ }
+
+ public String getCaptureRequestTemplates() {
+ StringBuffer templates = new StringBuffer("{\"CameraRequestTemplates\":{");
+ String[] cameraIds;
+ try {
+ cameraIds = mCameraManager.getCameraIdList();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+ return "";
+ }
+ CameraDevice camera = null;
+ for (String id : cameraIds) {
+ try {
+ try {
+ camera = (new BlockingCameraManager(mCameraManager)).openCamera(id,
+ mCameraListener, mHandler);
+ } catch (CameraAccessException | BlockingOpenException e) {
+ Log.e(TAG, "Unable to open camera " + id + " because of error "
+ + e.getMessage());
+ continue;
+ }
+
+ for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+ String value = null;
+ CaptureRequest.Builder request;
+ try {
+ request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+ value = serialize(request.build()).toString();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+ + " because of error " + e.getMessage());
+ }
+ templates.append("\"Camera" + id + "CaptureTemplate" +
+ TEMPLATE_IDS[i] + "\":");
+ templates.append(value);
+ if (!id.equals(cameraIds[cameraIds.length - 1]) ||
+ i < (TEMPLATE_IDS.length - 1)) {
+ templates.append(",");
+ }
+ }
+ } finally {
+ if (camera != null) {
+ camera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ }
+ }
+ }
+
+ templates.append("}}");
+ return templates.toString();
+ }
+
+ /*
+ * Cleanup the resources.
+ */
+ @Override
+ public void close() throws Exception {
+ mHandlerThread.quitSafely();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRational(Rational rat) throws org.json.JSONException {
+ JSONObject ratObj = new JSONObject();
+ ratObj.put("numerator", rat.getNumerator());
+ ratObj.put("denominator", rat.getDenominator());
+ return ratObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeSize(Size size) throws org.json.JSONException {
+ JSONObject sizeObj = new JSONObject();
+ sizeObj.put("width", size.getWidth());
+ sizeObj.put("height", size.getHeight());
+ return sizeObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeSizeF(SizeF size) throws org.json.JSONException {
+ JSONObject sizeObj = new JSONObject();
+ sizeObj.put("width", size.getWidth());
+ sizeObj.put("height", size.getHeight());
+ return sizeObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRect(Rect rect) throws org.json.JSONException {
+ JSONObject rectObj = new JSONObject();
+ rectObj.put("left", rect.left);
+ rectObj.put("right", rect.right);
+ rectObj.put("top", rect.top);
+ rectObj.put("bottom", rect.bottom);
+ return rectObj;
+ }
+
+ private static Object serializePoint(Point point) throws org.json.JSONException {
+ JSONObject pointObj = new JSONObject();
+ pointObj.put("x", point.x);
+ pointObj.put("y", point.y);
+ return pointObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeFace(Face face)
+ throws org.json.JSONException {
+ JSONObject faceObj = new JSONObject();
+ faceObj.put("bounds", serializeRect(face.getBounds()));
+ faceObj.put("score", face.getScore());
+ faceObj.put("id", face.getId());
+ faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
+ faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
+ faceObj.put("mouth", serializePoint(face.getMouthPosition()));
+ return faceObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeStreamConfigurationMap(
+ StreamConfigurationMap map)
+ throws org.json.JSONException {
+ // TODO: Serialize the rest of the StreamConfigurationMap fields.
+ JSONObject mapObj = new JSONObject();
+ JSONArray cfgArray = new JSONArray();
+ int fmts[] = map.getOutputFormats();
+ if (fmts != null) {
+ for (int fi = 0; fi < Array.getLength(fmts); fi++) {
+ Size sizes[] = map.getOutputSizes(fmts[fi]);
+ if (sizes != null) {
+ for (int si = 0; si < Array.getLength(sizes); si++) {
+ JSONObject obj = new JSONObject();
+ obj.put("format", fmts[fi]);
+ obj.put("width", sizes[si].getWidth());
+ obj.put("height", sizes[si].getHeight());
+ obj.put("input", false);
+ obj.put("minFrameDuration",
+ map.getOutputMinFrameDuration(fmts[fi], sizes[si]));
+ cfgArray.put(obj);
+ }
+ }
+ }
+ }
+ mapObj.put("availableStreamConfigurations", cfgArray);
+ return mapObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeMeteringRectangle(MeteringRectangle rect)
+ throws org.json.JSONException {
+ JSONObject rectObj = new JSONObject();
+ rectObj.put("x", rect.getX());
+ rectObj.put("y", rect.getY());
+ rectObj.put("width", rect.getWidth());
+ rectObj.put("height", rect.getHeight());
+ rectObj.put("weight", rect.getMeteringWeight());
+ return rectObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializePair(Pair pair)
+ throws org.json.JSONException {
+ JSONArray pairObj = new JSONArray();
+ pairObj.put(pair.first);
+ pairObj.put(pair.second);
+ return pairObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRange(Range range)
+ throws org.json.JSONException {
+ JSONArray rangeObj = new JSONArray();
+ rangeObj.put(range.getLower());
+ rangeObj.put(range.getUpper());
+ return rangeObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeColorSpaceTransform(ColorSpaceTransform xform)
+ throws org.json.JSONException {
+ JSONArray xformObj = new JSONArray();
+ for (int row = 0; row < 3; row++) {
+ for (int col = 0; col < 3; col++) {
+ xformObj.put(serializeRational(xform.getElement(col, row)));
+ }
+ }
+ return xformObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeTonemapCurve(TonemapCurve curve)
+ throws org.json.JSONException {
+ JSONObject curveObj = new JSONObject();
+ String names[] = {
+ "red", "green", "blue" };
+ for (int ch = 0; ch < 3; ch++) {
+ JSONArray curveArr = new JSONArray();
+ int len = curve.getPointCount(ch);
+ for (int i = 0; i < len; i++) {
+ curveArr.put(curve.getPoint(ch, i).x);
+ curveArr.put(curve.getPoint(ch, i).y);
+ }
+ curveObj.put(names[ch], curveArr);
+ }
+ return curveObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRggbChannelVector(RggbChannelVector vec)
+ throws org.json.JSONException {
+ JSONArray vecObj = new JSONArray();
+ vecObj.put(vec.getRed());
+ vecObj.put(vec.getGreenEven());
+ vecObj.put(vec.getGreenOdd());
+ vecObj.put(vec.getBlue());
+ return vecObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeBlackLevelPattern(BlackLevelPattern pat)
+ throws org.json.JSONException {
+ int patVals[] = new int[4];
+ pat.copyTo(patVals, 0);
+ JSONArray patObj = new JSONArray();
+ patObj.put(patVals[0]);
+ patObj.put(patVals[1]);
+ patObj.put(patVals[2]);
+ patObj.put(patVals[3]);
+ return patObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeLocation(Location loc)
+ throws org.json.JSONException {
+ return loc.toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeLensShadingMap(LensShadingMap map)
+ throws org.json.JSONException {
+ JSONArray mapObj = new JSONArray();
+ for (int row = 0; row < map.getRowCount(); row++) {
+ for (int col = 0; col < map.getColumnCount(); col++) {
+ for (int ch = 0; ch < 4; ch++) {
+ mapObj.put(map.getGainFactor(ch, col, row));
+ }
+ }
+ }
+ return mapObj;
+ }
+
+ private static String getKeyName(Object keyObj) {
+ if (keyObj.getClass() == CaptureResult.Key.class
+ || keyObj.getClass() == TotalCaptureResult.class) {
+ return ((CaptureResult.Key) keyObj).getName();
+ } else if (keyObj.getClass() == CaptureRequest.Key.class) {
+ return ((CaptureRequest.Key) keyObj).getName();
+ } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
+ return ((CameraCharacteristics.Key) keyObj).getName();
+ }
+
+ throw new IllegalArgumentException("Invalid key object");
+ }
+
+ private static Object getKeyValue(CameraMetadata md, Object keyObj) {
+ if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
+ return ((CaptureResult) md).get((CaptureResult.Key) keyObj);
+ } else if (md.getClass() == CaptureRequest.class) {
+ return ((CaptureRequest) md).get((CaptureRequest.Key) keyObj);
+ } else if (md.getClass() == CameraCharacteristics.class) {
+ return ((CameraCharacteristics) md).get((CameraCharacteristics.Key) keyObj);
+ }
+
+ throw new IllegalArgumentException("Invalid key object");
+ }
+
+ @SuppressWarnings("unchecked")
+ private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md) {
+ String keyName = getKeyName(keyObj);
+
+ try {
+ Object keyValue = getKeyValue(md, keyObj);
+ if (keyValue == null) {
+ return new MetadataEntry(keyName, JSONObject.NULL);
+ } else if (keyType == Float.class) {
+ // The JSON serializer doesn't handle floating point NaN or Inf.
+ if (((Float) keyValue).isInfinite() || ((Float) keyValue).isNaN()) {
+ Log.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
+ return null;
+ }
+ return new MetadataEntry(keyName, keyValue);
+ } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
+ keyType == Boolean.class || keyType == String.class) {
+ return new MetadataEntry(keyName, keyValue);
+ } else if (keyType == Rational.class) {
+ return new MetadataEntry(keyName, serializeRational((Rational) keyValue));
+ } else if (keyType == Size.class) {
+ return new MetadataEntry(keyName, serializeSize((Size) keyValue));
+ } else if (keyType == SizeF.class) {
+ return new MetadataEntry(keyName, serializeSizeF((SizeF) keyValue));
+ } else if (keyType == Rect.class) {
+ return new MetadataEntry(keyName, serializeRect((Rect) keyValue));
+ } else if (keyType == Face.class) {
+ return new MetadataEntry(keyName, serializeFace((Face) keyValue));
+ } else if (keyType == StreamConfigurationMap.class) {
+ return new MetadataEntry(keyName,
+ serializeStreamConfigurationMap((StreamConfigurationMap) keyValue));
+ } else if (keyType instanceof ParameterizedType &&
+ ((ParameterizedType) keyType).getRawType() == Range.class) {
+ return new MetadataEntry(keyName, serializeRange((Range) keyValue));
+ } else if (keyType == ColorSpaceTransform.class) {
+ return new MetadataEntry(keyName,
+ serializeColorSpaceTransform((ColorSpaceTransform) keyValue));
+ } else if (keyType == MeteringRectangle.class) {
+ return new MetadataEntry(keyName,
+ serializeMeteringRectangle((MeteringRectangle) keyValue));
+ } else if (keyType == Location.class) {
+ return new MetadataEntry(keyName,
+ serializeLocation((Location) keyValue));
+ } else if (keyType == RggbChannelVector.class) {
+ return new MetadataEntry(keyName,
+ serializeRggbChannelVector((RggbChannelVector) keyValue));
+ } else if (keyType == BlackLevelPattern.class) {
+ return new MetadataEntry(keyName,
+ serializeBlackLevelPattern((BlackLevelPattern) keyValue));
+ } else if (keyType == TonemapCurve.class) {
+ return new MetadataEntry(keyName,
+ serializeTonemapCurve((TonemapCurve) keyValue));
+ } else if (keyType == Point.class) {
+ return new MetadataEntry(keyName,
+ serializePoint((Point) keyValue));
+ } else if (keyType == LensShadingMap.class) {
+ return new MetadataEntry(keyName,
+ serializeLensShadingMap((LensShadingMap) keyValue));
+ } else {
+ Log.w(TAG, String.format("Serializing unsupported key type: " + keyType));
+ return null;
+ }
+ } catch (org.json.JSONException e) {
+ throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj,
+ CameraMetadata md) {
+ String keyName = getKeyName(keyObj);
+ try {
+ Object keyValue = getKeyValue(md, keyObj);
+ if (keyValue == null) {
+ return new MetadataEntry(keyName, JSONObject.NULL);
+ }
+ int arrayLen = Array.getLength(keyValue);
+ Type elmtType = ((GenericArrayType) keyType).getGenericComponentType();
+ if (elmtType == int.class || elmtType == float.class || elmtType == byte.class ||
+ elmtType == long.class || elmtType == double.class
+ || elmtType == boolean.class) {
+ return new MetadataEntry(keyName, new JSONArray(keyValue));
+ } else if (elmtType == Rational.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRational((Rational) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Size.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeSize((Size) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Rect.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRect((Rect) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Face.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeFace((Face) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == StreamConfigurationMap.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeStreamConfigurationMap(
+ (StreamConfigurationMap) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType instanceof ParameterizedType &&
+ ((ParameterizedType) elmtType).getRawType() == Range.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRange((Range) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType instanceof ParameterizedType &&
+ ((ParameterizedType) elmtType).getRawType() == Pair.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializePair((Pair) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == MeteringRectangle.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeMeteringRectangle(
+ (MeteringRectangle) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Location.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeLocation((Location) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == RggbChannelVector.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRggbChannelVector(
+ (RggbChannelVector) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == BlackLevelPattern.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeBlackLevelPattern(
+ (BlackLevelPattern) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Point.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializePoint((Point) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else {
+ Log.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
+ return null;
+ }
+ } catch (org.json.JSONException e) {
+ throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static JSONObject serialize(CameraMetadata md) {
+ JSONObject jsonObj = new JSONObject();
+ Field[] allFields = md.getClass().getDeclaredFields();
+ if (md.getClass() == TotalCaptureResult.class) {
+ allFields = CaptureResult.class.getDeclaredFields();
+ }
+ for (Field field : allFields) {
+ if (Modifier.isPublic(field.getModifiers()) &&
+ Modifier.isStatic(field.getModifiers()) &&
+ (field.getType() == CaptureRequest.Key.class
+ || field.getType() == CaptureResult.Key.class
+ || field.getType() == TotalCaptureResult.Key.class
+ || field.getType() == CameraCharacteristics.Key.class)
+ &&
+ field.getGenericType() instanceof ParameterizedType) {
+ ParameterizedType paramType = (ParameterizedType) field.getGenericType();
+ Type[] argTypes = paramType.getActualTypeArguments();
+ if (argTypes.length > 0) {
+ try {
+ Type keyType = argTypes[0];
+ Object keyObj = field.get(md);
+ MetadataEntry entry;
+ if (keyType instanceof GenericArrayType) {
+ entry = serializeArrayEntry(keyType, keyObj, md);
+ } else {
+ entry = serializeEntry(keyType, keyObj, md);
+ }
+
+ // TODO: Figure this weird case out.
+ // There is a weird case where the entry is non-null but
+ // the toString
+ // of the entry is null, and if this happens, the
+ // null-ness spreads like
+ // a virus and makes the whole JSON object null from the
+ // top level down.
+ // Not sure if it's a bug in the library or I'm just not
+ // using it right.
+ // Workaround by checking for this case explicitly and
+ // not adding the
+ // value to the jsonObj when it is detected.
+ if (entry != null && entry.key != null && entry.value != null
+ && entry.value.toString() == null) {
+ Log.w(TAG, "Error encountered serializing value for key: "
+ + entry.key);
+ } else if (entry != null) {
+ jsonObj.put(entry.key, entry.value);
+ } else {
+ // Ignore.
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ "Access error for field: " + field + ": ", e);
+ } catch (org.json.JSONException e) {
+ throw new IllegalStateException(
+ "JSON error for field: " + field + ": ", e);
+ }
+ }
+ }
+ }
+ return jsonObj;
+ }
+}