Home | History | Annotate | Download | only in helpers
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.hardware.camera2.cts.helpers;
     18 
     19 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
     20 
     21 import android.graphics.Point;
     22 import android.graphics.Rect;
     23 import android.hardware.camera2.CameraAccessException;
     24 import android.hardware.camera2.CameraCharacteristics;
     25 import android.hardware.camera2.CameraDevice;
     26 import android.hardware.camera2.CameraManager;
     27 import android.hardware.camera2.CameraMetadata;
     28 import android.hardware.camera2.CaptureResult;
     29 import android.hardware.camera2.CaptureRequest;
     30 import android.hardware.camera2.TotalCaptureResult;
     31 import android.hardware.camera2.params.BlackLevelPattern;
     32 import android.hardware.camera2.params.ColorSpaceTransform;
     33 import android.hardware.camera2.params.Face;
     34 import android.hardware.camera2.params.LensShadingMap;
     35 import android.hardware.camera2.params.MeteringRectangle;
     36 import android.hardware.camera2.params.RggbChannelVector;
     37 import android.hardware.camera2.params.StreamConfigurationMap;
     38 import android.hardware.camera2.params.TonemapCurve;
     39 import android.location.Location;
     40 import android.os.Handler;
     41 import android.os.HandlerThread;
     42 import android.util.Log;
     43 import android.util.Pair;
     44 import android.util.Rational;
     45 import android.util.Size;
     46 import android.util.SizeF;
     47 import android.util.Range;
     48 
     49 import com.android.ex.camera2.blocking.BlockingCameraManager;
     50 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
     51 import com.android.ex.camera2.blocking.BlockingStateCallback;
     52 
     53 import org.json.JSONArray;
     54 import org.json.JSONObject;
     55 
     56 import java.lang.reflect.Array;
     57 import java.lang.reflect.Field;
     58 import java.lang.reflect.GenericArrayType;
     59 import java.lang.reflect.Modifier;
     60 import java.lang.reflect.ParameterizedType;
     61 import java.lang.reflect.Type;
     62 
     63 /**
     64  * Utility class to dump the camera metadata.
     65  */
     66 public final class CameraMetadataGetter implements AutoCloseable {
     67     private static final String TAG = CameraMetadataGetter.class.getSimpleName();
     68     private static final int CAMERA_CLOSE_TIMEOUT_MS = 5000;
     69     public static final int[] TEMPLATE_IDS = {
     70         CameraDevice.TEMPLATE_PREVIEW,
     71         CameraDevice.TEMPLATE_STILL_CAPTURE,
     72         CameraDevice.TEMPLATE_RECORD,
     73         CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
     74         CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
     75         CameraDevice.TEMPLATE_MANUAL,
     76     };
     77     private CameraManager mCameraManager;
     78     private BlockingStateCallback mCameraListener;
     79     private HandlerThread mHandlerThread;
     80     private Handler mHandler;
     81 
     82     private static class MetadataEntry {
     83         public MetadataEntry(String k, Object v) {
     84             key = k;
     85             value = v;
     86         }
     87 
     88         public String key;
     89         public Object value;
     90     }
     91 
     92     public CameraMetadataGetter(CameraManager cameraManager) {
     93         if (cameraManager == null) {
     94             throw new IllegalArgumentException("can not create an CameraMetadataGetter object"
     95                     + " with null CameraManager");
     96         }
     97 
     98         mCameraManager = cameraManager;
     99 
    100         mCameraListener = new BlockingStateCallback();
    101         mHandlerThread = new HandlerThread(TAG);
    102         mHandlerThread.start();
    103         mHandler = new Handler(mHandlerThread.getLooper());
    104     }
    105 
    106     public String getCameraInfo() {
    107         StringBuffer cameraInfo = new StringBuffer("{\"CameraStaticMetadata\":{");
    108         CameraCharacteristics staticMetadata;
    109         String[] cameraIds;
    110         try {
    111             cameraIds = mCameraManager.getCameraIdList();
    112         } catch (CameraAccessException e) {
    113             Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
    114             return "";
    115         }
    116         for (String id : cameraIds) {
    117             String value = null;
    118             try {
    119                 staticMetadata = mCameraManager.getCameraCharacteristics(id);
    120                 value = serialize(staticMetadata).toString();
    121             } catch (CameraAccessException e) {
    122                 Log.e(TAG,
    123                         "Unable to get camera camera static info, skip this camera, error: "
    124                                 + e.getMessage());
    125             }
    126             cameraInfo.append("\"camera" + id + "\":"); // Key
    127             cameraInfo.append(value); // Value
    128             // If not last, print "," // Separator
    129             if (!id.equals(cameraIds[cameraIds.length - 1])) {
    130                 cameraInfo.append(",");
    131             }
    132         }
    133         cameraInfo.append("}}");
    134 
    135         return cameraInfo.toString();
    136     }
    137 
    138     public JSONObject getCameraInfo(String cameraId) {
    139         JSONObject staticMetadata = null;
    140         try {
    141             staticMetadata = serialize(mCameraManager.getCameraCharacteristics(cameraId));
    142         } catch (CameraAccessException e) {
    143             Log.e(TAG,
    144                     "Unable to get camera camera static info, skip this camera, error: "
    145                             + e.getMessage());
    146         }
    147         return staticMetadata;
    148     }
    149 
    150     public JSONObject[] getCaptureRequestTemplates(String cameraId) {
    151         JSONObject[] templates = new JSONObject[TEMPLATE_IDS.length];
    152         CameraDevice camera = null;
    153         try {
    154             camera = (new BlockingCameraManager(mCameraManager)).openCamera(cameraId,
    155                             mCameraListener, mHandler);
    156             for (int i = 0; i < TEMPLATE_IDS.length; i++) {
    157                 CaptureRequest.Builder request;
    158                 try {
    159                     request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
    160                     templates[i] = serialize(request.build());
    161                 } catch (Exception e) {
    162                     Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
    163                                     + " because of error " + e.getMessage());
    164                     templates[i] = null;
    165                 }
    166             }
    167             return templates;
    168         } catch (CameraAccessException | BlockingOpenException e) {
    169             Log.e(TAG, "Unable to open camera " + cameraId + " because of error "
    170                             + e.getMessage());
    171             return new JSONObject[0];
    172         } finally {
    173             if (camera != null) {
    174                 camera.close();
    175             }
    176         }
    177     }
    178 
    179     public String getCaptureRequestTemplates() {
    180         StringBuffer templates = new StringBuffer("{\"CameraRequestTemplates\":{");
    181         String[] cameraIds;
    182         try {
    183             cameraIds = mCameraManager.getCameraIdList();
    184         } catch (CameraAccessException e) {
    185             Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
    186             return "";
    187         }
    188         CameraDevice camera = null;
    189         for (String id : cameraIds) {
    190             try {
    191                 try {
    192                     camera = (new BlockingCameraManager(mCameraManager)).openCamera(id,
    193                                     mCameraListener, mHandler);
    194                 } catch (CameraAccessException | BlockingOpenException e) {
    195                     Log.e(TAG, "Unable to open camera " + id + " because of error "
    196                                     + e.getMessage());
    197                     continue;
    198                 }
    199 
    200                 for (int i = 0; i < TEMPLATE_IDS.length; i++) {
    201                     String value = null;
    202                     CaptureRequest.Builder request;
    203                     try {
    204                         request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
    205                         value = serialize(request.build()).toString();
    206                     } catch (Exception e) {
    207                         Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
    208                                         + " because of error " + e.getMessage());
    209                     }
    210                     templates.append("\"Camera" + id + "CaptureTemplate" +
    211                                     TEMPLATE_IDS[i] + "\":");
    212                     templates.append(value);
    213                     if (!id.equals(cameraIds[cameraIds.length - 1]) ||
    214                                     i < (TEMPLATE_IDS.length - 1)) {
    215                         templates.append(",");
    216                     }
    217                 }
    218             } finally {
    219                 if (camera != null) {
    220                     camera.close();
    221                     mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
    222                 }
    223             }
    224         }
    225 
    226         templates.append("}}");
    227         return templates.toString();
    228     }
    229 
    230     /*
    231      * Cleanup the resources.
    232      */
    233     @Override
    234     public void close() throws Exception {
    235         mHandlerThread.quitSafely();
    236     }
    237 
    238     @Override
    239     protected void finalize() throws Throwable {
    240         try {
    241             close();
    242         } finally {
    243             super.finalize();
    244         }
    245     }
    246 
    247     @SuppressWarnings("unchecked")
    248     private static Object serializeRational(Rational rat) throws org.json.JSONException {
    249         JSONObject ratObj = new JSONObject();
    250         ratObj.put("numerator", rat.getNumerator());
    251         ratObj.put("denominator", rat.getDenominator());
    252         return ratObj;
    253     }
    254 
    255     @SuppressWarnings("unchecked")
    256     private static Object serializeSize(Size size) throws org.json.JSONException {
    257         JSONObject sizeObj = new JSONObject();
    258         sizeObj.put("width", size.getWidth());
    259         sizeObj.put("height", size.getHeight());
    260         return sizeObj;
    261     }
    262 
    263     @SuppressWarnings("unchecked")
    264     private static Object serializeSizeF(SizeF size) throws org.json.JSONException {
    265         JSONObject sizeObj = new JSONObject();
    266         sizeObj.put("width", size.getWidth());
    267         sizeObj.put("height", size.getHeight());
    268         return sizeObj;
    269     }
    270 
    271     @SuppressWarnings("unchecked")
    272     private static Object serializeRect(Rect rect) throws org.json.JSONException {
    273         JSONObject rectObj = new JSONObject();
    274         rectObj.put("left", rect.left);
    275         rectObj.put("right", rect.right);
    276         rectObj.put("top", rect.top);
    277         rectObj.put("bottom", rect.bottom);
    278         return rectObj;
    279     }
    280 
    281     private static Object serializePoint(Point point) throws org.json.JSONException {
    282         JSONObject pointObj = new JSONObject();
    283         pointObj.put("x", point.x);
    284         pointObj.put("y", point.y);
    285         return pointObj;
    286     }
    287 
    288     @SuppressWarnings("unchecked")
    289     private static Object serializeFace(Face face)
    290                     throws org.json.JSONException {
    291         JSONObject faceObj = new JSONObject();
    292         faceObj.put("bounds", serializeRect(face.getBounds()));
    293         faceObj.put("score", face.getScore());
    294         faceObj.put("id", face.getId());
    295         faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
    296         faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
    297         faceObj.put("mouth", serializePoint(face.getMouthPosition()));
    298         return faceObj;
    299     }
    300 
    301     @SuppressWarnings("unchecked")
    302     private static Object serializeStreamConfigurationMap(
    303                     StreamConfigurationMap map)
    304                     throws org.json.JSONException {
    305         // TODO: Serialize the rest of the StreamConfigurationMap fields.
    306         JSONObject mapObj = new JSONObject();
    307         JSONArray cfgArray = new JSONArray();
    308         int fmts[] = map.getOutputFormats();
    309         if (fmts != null) {
    310             for (int fi = 0; fi < Array.getLength(fmts); fi++) {
    311                 Size sizes[] = map.getOutputSizes(fmts[fi]);
    312                 if (sizes != null) {
    313                     for (int si = 0; si < Array.getLength(sizes); si++) {
    314                         JSONObject obj = new JSONObject();
    315                         obj.put("format", fmts[fi]);
    316                         obj.put("width", sizes[si].getWidth());
    317                         obj.put("height", sizes[si].getHeight());
    318                         obj.put("input", false);
    319                         obj.put("minFrameDuration",
    320                                         map.getOutputMinFrameDuration(fmts[fi], sizes[si]));
    321                         cfgArray.put(obj);
    322                     }
    323                 }
    324             }
    325         }
    326         mapObj.put("availableStreamConfigurations", cfgArray);
    327         return mapObj;
    328     }
    329 
    330     @SuppressWarnings("unchecked")
    331     private static Object serializeMeteringRectangle(MeteringRectangle rect)
    332                     throws org.json.JSONException {
    333         JSONObject rectObj = new JSONObject();
    334         rectObj.put("x", rect.getX());
    335         rectObj.put("y", rect.getY());
    336         rectObj.put("width", rect.getWidth());
    337         rectObj.put("height", rect.getHeight());
    338         rectObj.put("weight", rect.getMeteringWeight());
    339         return rectObj;
    340     }
    341 
    342     @SuppressWarnings("unchecked")
    343     private static Object serializePair(Pair pair)
    344                     throws org.json.JSONException {
    345         JSONArray pairObj = new JSONArray();
    346         pairObj.put(pair.first);
    347         pairObj.put(pair.second);
    348         return pairObj;
    349     }
    350 
    351     @SuppressWarnings("unchecked")
    352     private static Object serializeRange(Range range)
    353                     throws org.json.JSONException {
    354         JSONArray rangeObj = new JSONArray();
    355         rangeObj.put(range.getLower());
    356         rangeObj.put(range.getUpper());
    357         return rangeObj;
    358     }
    359 
    360     @SuppressWarnings("unchecked")
    361     private static Object serializeColorSpaceTransform(ColorSpaceTransform xform)
    362                     throws org.json.JSONException {
    363         JSONArray xformObj = new JSONArray();
    364         for (int row = 0; row < 3; row++) {
    365             for (int col = 0; col < 3; col++) {
    366                 xformObj.put(serializeRational(xform.getElement(col, row)));
    367             }
    368         }
    369         return xformObj;
    370     }
    371 
    372     @SuppressWarnings("unchecked")
    373     private static Object serializeTonemapCurve(TonemapCurve curve)
    374                     throws org.json.JSONException {
    375         JSONObject curveObj = new JSONObject();
    376         String names[] = {
    377                         "red", "green", "blue" };
    378         for (int ch = 0; ch < 3; ch++) {
    379             JSONArray curveArr = new JSONArray();
    380             int len = curve.getPointCount(ch);
    381             for (int i = 0; i < len; i++) {
    382                 curveArr.put(curve.getPoint(ch, i).x);
    383                 curveArr.put(curve.getPoint(ch, i).y);
    384             }
    385             curveObj.put(names[ch], curveArr);
    386         }
    387         return curveObj;
    388     }
    389 
    390     @SuppressWarnings("unchecked")
    391     private static Object serializeRggbChannelVector(RggbChannelVector vec)
    392                     throws org.json.JSONException {
    393         JSONArray vecObj = new JSONArray();
    394         vecObj.put(vec.getRed());
    395         vecObj.put(vec.getGreenEven());
    396         vecObj.put(vec.getGreenOdd());
    397         vecObj.put(vec.getBlue());
    398         return vecObj;
    399     }
    400 
    401     @SuppressWarnings("unchecked")
    402     private static Object serializeBlackLevelPattern(BlackLevelPattern pat)
    403                     throws org.json.JSONException {
    404         int patVals[] = new int[4];
    405         pat.copyTo(patVals, 0);
    406         JSONArray patObj = new JSONArray();
    407         patObj.put(patVals[0]);
    408         patObj.put(patVals[1]);
    409         patObj.put(patVals[2]);
    410         patObj.put(patVals[3]);
    411         return patObj;
    412     }
    413 
    414     @SuppressWarnings("unchecked")
    415     private static Object serializeLocation(Location loc)
    416                     throws org.json.JSONException {
    417         return loc.toString();
    418     }
    419 
    420     @SuppressWarnings("unchecked")
    421     private static Object serializeLensShadingMap(LensShadingMap map)
    422             throws org.json.JSONException {
    423         JSONArray mapObj = new JSONArray();
    424         for (int row = 0; row < map.getRowCount(); row++) {
    425             for (int col = 0; col < map.getColumnCount(); col++) {
    426                 for (int ch = 0; ch < 4; ch++) {
    427                     mapObj.put(map.getGainFactor(ch, col, row));
    428                 }
    429             }
    430         }
    431         return mapObj;
    432     }
    433 
    434     private static String getKeyName(Object keyObj) {
    435         if (keyObj.getClass() == CaptureResult.Key.class
    436                 || keyObj.getClass() == TotalCaptureResult.class) {
    437             return ((CaptureResult.Key) keyObj).getName();
    438         } else if (keyObj.getClass() == CaptureRequest.Key.class) {
    439             return ((CaptureRequest.Key) keyObj).getName();
    440         } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
    441             return ((CameraCharacteristics.Key) keyObj).getName();
    442         }
    443 
    444         throw new IllegalArgumentException("Invalid key object");
    445     }
    446 
    447     private static Object getKeyValue(CameraMetadata md, Object keyObj) {
    448         if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
    449             return ((CaptureResult) md).get((CaptureResult.Key) keyObj);
    450         } else if (md.getClass() == CaptureRequest.class) {
    451             return ((CaptureRequest) md).get((CaptureRequest.Key) keyObj);
    452         } else if (md.getClass() == CameraCharacteristics.class) {
    453             return ((CameraCharacteristics) md).get((CameraCharacteristics.Key) keyObj);
    454         }
    455 
    456         throw new IllegalArgumentException("Invalid key object");
    457     }
    458 
    459     @SuppressWarnings("unchecked")
    460     private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md) {
    461         String keyName = getKeyName(keyObj);
    462 
    463         try {
    464             Object keyValue = getKeyValue(md, keyObj);
    465             if (keyValue == null) {
    466                 return new MetadataEntry(keyName, JSONObject.NULL);
    467             } else if (keyType == Float.class) {
    468                 // The JSON serializer doesn't handle floating point NaN or Inf.
    469                 if (((Float) keyValue).isInfinite() || ((Float) keyValue).isNaN()) {
    470                     Log.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
    471                     return null;
    472                 }
    473                 return new MetadataEntry(keyName, keyValue);
    474             } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
    475                     keyType == Boolean.class || keyType == String.class) {
    476                 return new MetadataEntry(keyName, keyValue);
    477             } else if (keyType == Rational.class) {
    478                 return new MetadataEntry(keyName, serializeRational((Rational) keyValue));
    479             } else if (keyType == Size.class) {
    480                 return new MetadataEntry(keyName, serializeSize((Size) keyValue));
    481             } else if (keyType == SizeF.class) {
    482                 return new MetadataEntry(keyName, serializeSizeF((SizeF) keyValue));
    483             } else if (keyType == Rect.class) {
    484                 return new MetadataEntry(keyName, serializeRect((Rect) keyValue));
    485             } else if (keyType == Face.class) {
    486                 return new MetadataEntry(keyName, serializeFace((Face) keyValue));
    487             } else if (keyType == StreamConfigurationMap.class) {
    488                 return new MetadataEntry(keyName,
    489                         serializeStreamConfigurationMap((StreamConfigurationMap) keyValue));
    490             } else if (keyType instanceof ParameterizedType &&
    491                     ((ParameterizedType) keyType).getRawType() == Range.class) {
    492                 return new MetadataEntry(keyName, serializeRange((Range) keyValue));
    493             } else if (keyType == ColorSpaceTransform.class) {
    494                 return new MetadataEntry(keyName,
    495                         serializeColorSpaceTransform((ColorSpaceTransform) keyValue));
    496             } else if (keyType == MeteringRectangle.class) {
    497                 return new MetadataEntry(keyName,
    498                         serializeMeteringRectangle((MeteringRectangle) keyValue));
    499             } else if (keyType == Location.class) {
    500                 return new MetadataEntry(keyName,
    501                         serializeLocation((Location) keyValue));
    502             } else if (keyType == RggbChannelVector.class) {
    503                 return new MetadataEntry(keyName,
    504                         serializeRggbChannelVector((RggbChannelVector) keyValue));
    505             } else if (keyType == BlackLevelPattern.class) {
    506                 return new MetadataEntry(keyName,
    507                         serializeBlackLevelPattern((BlackLevelPattern) keyValue));
    508             } else if (keyType == TonemapCurve.class) {
    509                 return new MetadataEntry(keyName,
    510                         serializeTonemapCurve((TonemapCurve) keyValue));
    511             } else if (keyType == Point.class) {
    512                 return new MetadataEntry(keyName,
    513                         serializePoint((Point) keyValue));
    514             } else if (keyType == LensShadingMap.class) {
    515                 return new MetadataEntry(keyName,
    516                         serializeLensShadingMap((LensShadingMap) keyValue));
    517             } else {
    518                 Log.w(TAG, String.format("Serializing unsupported key type: " + keyType));
    519                 return null;
    520             }
    521         } catch (org.json.JSONException e) {
    522             throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
    523         }
    524     }
    525 
    526     @SuppressWarnings("unchecked")
    527     private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj,
    528             CameraMetadata md) {
    529         String keyName = getKeyName(keyObj);
    530         try {
    531             Object keyValue = getKeyValue(md, keyObj);
    532             if (keyValue == null) {
    533                 return new MetadataEntry(keyName, JSONObject.NULL);
    534             }
    535             int arrayLen = Array.getLength(keyValue);
    536             Type elmtType = ((GenericArrayType) keyType).getGenericComponentType();
    537             if (elmtType == int.class || elmtType == float.class || elmtType == byte.class ||
    538                     elmtType == long.class || elmtType == double.class
    539                     || elmtType == boolean.class) {
    540                 return new MetadataEntry(keyName, new JSONArray(keyValue));
    541             } else if (elmtType == Rational.class) {
    542                 JSONArray jsonArray = new JSONArray();
    543                 for (int i = 0; i < arrayLen; i++) {
    544                     jsonArray.put(serializeRational((Rational) Array.get(keyValue, i)));
    545                 }
    546                 return new MetadataEntry(keyName, jsonArray);
    547             } else if (elmtType == Size.class) {
    548                 JSONArray jsonArray = new JSONArray();
    549                 for (int i = 0; i < arrayLen; i++) {
    550                     jsonArray.put(serializeSize((Size) Array.get(keyValue, i)));
    551                 }
    552                 return new MetadataEntry(keyName, jsonArray);
    553             } else if (elmtType == Rect.class) {
    554                 JSONArray jsonArray = new JSONArray();
    555                 for (int i = 0; i < arrayLen; i++) {
    556                     jsonArray.put(serializeRect((Rect) Array.get(keyValue, i)));
    557                 }
    558                 return new MetadataEntry(keyName, jsonArray);
    559             } else if (elmtType == Face.class) {
    560                 JSONArray jsonArray = new JSONArray();
    561                 for (int i = 0; i < arrayLen; i++) {
    562                     jsonArray.put(serializeFace((Face) Array.get(keyValue, i)));
    563                 }
    564                 return new MetadataEntry(keyName, jsonArray);
    565             } else if (elmtType == StreamConfigurationMap.class) {
    566                 JSONArray jsonArray = new JSONArray();
    567                 for (int i = 0; i < arrayLen; i++) {
    568                     jsonArray.put(serializeStreamConfigurationMap(
    569                             (StreamConfigurationMap) Array.get(keyValue, i)));
    570                 }
    571                 return new MetadataEntry(keyName, jsonArray);
    572             } else if (elmtType instanceof ParameterizedType &&
    573                     ((ParameterizedType) elmtType).getRawType() == Range.class) {
    574                 JSONArray jsonArray = new JSONArray();
    575                 for (int i = 0; i < arrayLen; i++) {
    576                     jsonArray.put(serializeRange((Range) Array.get(keyValue, i)));
    577                 }
    578                 return new MetadataEntry(keyName, jsonArray);
    579             } else if (elmtType instanceof ParameterizedType &&
    580                     ((ParameterizedType) elmtType).getRawType() == Pair.class) {
    581                 JSONArray jsonArray = new JSONArray();
    582                 for (int i = 0; i < arrayLen; i++) {
    583                     jsonArray.put(serializePair((Pair) Array.get(keyValue, i)));
    584                 }
    585                 return new MetadataEntry(keyName, jsonArray);
    586             } else if (elmtType == MeteringRectangle.class) {
    587                 JSONArray jsonArray = new JSONArray();
    588                 for (int i = 0; i < arrayLen; i++) {
    589                     jsonArray.put(serializeMeteringRectangle(
    590                             (MeteringRectangle) Array.get(keyValue, i)));
    591                 }
    592                 return new MetadataEntry(keyName, jsonArray);
    593             } else if (elmtType == Location.class) {
    594                 JSONArray jsonArray = new JSONArray();
    595                 for (int i = 0; i < arrayLen; i++) {
    596                     jsonArray.put(serializeLocation((Location) Array.get(keyValue, i)));
    597                 }
    598                 return new MetadataEntry(keyName, jsonArray);
    599             } else if (elmtType == RggbChannelVector.class) {
    600                 JSONArray jsonArray = new JSONArray();
    601                 for (int i = 0; i < arrayLen; i++) {
    602                     jsonArray.put(serializeRggbChannelVector(
    603                             (RggbChannelVector) Array.get(keyValue, i)));
    604                 }
    605                 return new MetadataEntry(keyName, jsonArray);
    606             } else if (elmtType == BlackLevelPattern.class) {
    607                 JSONArray jsonArray = new JSONArray();
    608                 for (int i = 0; i < arrayLen; i++) {
    609                     jsonArray.put(serializeBlackLevelPattern(
    610                             (BlackLevelPattern) Array.get(keyValue, i)));
    611                 }
    612                 return new MetadataEntry(keyName, jsonArray);
    613             } else if (elmtType == Point.class) {
    614                 JSONArray jsonArray = new JSONArray();
    615                 for (int i = 0; i < arrayLen; i++) {
    616                     jsonArray.put(serializePoint((Point) Array.get(keyValue, i)));
    617                 }
    618                 return new MetadataEntry(keyName, jsonArray);
    619             } else {
    620                 Log.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
    621                 return null;
    622             }
    623         } catch (org.json.JSONException e) {
    624             throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
    625         }
    626     }
    627 
    628     @SuppressWarnings("unchecked")
    629     private static JSONObject serialize(CameraMetadata md) {
    630         JSONObject jsonObj = new JSONObject();
    631         Field[] allFields = md.getClass().getDeclaredFields();
    632         if (md.getClass() == TotalCaptureResult.class) {
    633             allFields = CaptureResult.class.getDeclaredFields();
    634         }
    635         for (Field field : allFields) {
    636             if (Modifier.isPublic(field.getModifiers()) &&
    637                     Modifier.isStatic(field.getModifiers()) &&
    638                             (field.getType() == CaptureRequest.Key.class
    639                             || field.getType() == CaptureResult.Key.class
    640                             || field.getType() == TotalCaptureResult.Key.class
    641                             || field.getType() == CameraCharacteristics.Key.class)
    642                     &&
    643                     field.getGenericType() instanceof ParameterizedType) {
    644                 ParameterizedType paramType = (ParameterizedType) field.getGenericType();
    645                 Type[] argTypes = paramType.getActualTypeArguments();
    646                 if (argTypes.length > 0) {
    647                     try {
    648                         Type keyType = argTypes[0];
    649                         Object keyObj = field.get(md);
    650                         MetadataEntry entry;
    651                         if (keyType instanceof GenericArrayType) {
    652                             entry = serializeArrayEntry(keyType, keyObj, md);
    653                         } else {
    654                             entry = serializeEntry(keyType, keyObj, md);
    655                         }
    656 
    657                         // TODO: Figure this weird case out.
    658                         // There is a weird case where the entry is non-null but
    659                         // the toString
    660                         // of the entry is null, and if this happens, the
    661                         // null-ness spreads like
    662                         // a virus and makes the whole JSON object null from the
    663                         // top level down.
    664                         // Not sure if it's a bug in the library or I'm just not
    665                         // using it right.
    666                         // Workaround by checking for this case explicitly and
    667                         // not adding the
    668                         // value to the jsonObj when it is detected.
    669                         if (entry != null && entry.key != null && entry.value != null
    670                                 && entry.value.toString() == null) {
    671                             Log.w(TAG, "Error encountered serializing value for key: "
    672                                     + entry.key);
    673                         } else if (entry != null) {
    674                             jsonObj.put(entry.key, entry.value);
    675                         } else {
    676                             // Ignore.
    677                         }
    678                     } catch (IllegalAccessException e) {
    679                         throw new IllegalStateException(
    680                                 "Access error for field: " + field + ": ", e);
    681                     } catch (org.json.JSONException e) {
    682                         throw new IllegalStateException(
    683                                 "JSON error for field: " + field + ": ", e);
    684                     }
    685                 }
    686             }
    687         }
    688         return jsonObj;
    689     }
    690 }
    691