Home | History | Annotate | Download | only in its
      1 /*
      2  * Copyright (C) 2013 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 com.android.cts.verifier.camera.its;
     18 
     19 import android.graphics.Point;
     20 import android.graphics.Rect;
     21 import android.hardware.camera2.CameraCharacteristics;
     22 import android.hardware.camera2.CameraDevice;
     23 import android.hardware.camera2.CameraMetadata;
     24 import android.hardware.camera2.CaptureResult;
     25 import android.hardware.camera2.CaptureRequest;
     26 import android.hardware.camera2.TotalCaptureResult;
     27 import android.hardware.camera2.params.BlackLevelPattern;
     28 import android.hardware.camera2.params.ColorSpaceTransform;
     29 import android.hardware.camera2.params.Face;
     30 import android.hardware.camera2.params.LensShadingMap;
     31 import android.hardware.camera2.params.MeteringRectangle;
     32 import android.hardware.camera2.params.RggbChannelVector;
     33 import android.hardware.camera2.params.StreamConfigurationMap;
     34 import android.hardware.camera2.params.TonemapCurve;
     35 import android.location.Location;
     36 import android.util.Log;
     37 import android.util.Pair;
     38 import android.util.Rational;
     39 import android.util.Size;
     40 import android.util.SizeF;
     41 import android.util.Range;
     42 
     43 import org.json.JSONArray;
     44 import org.json.JSONObject;
     45 
     46 import java.lang.reflect.Array;
     47 import java.lang.reflect.Field;
     48 import java.lang.reflect.GenericArrayType;
     49 import java.lang.reflect.Modifier;
     50 import java.lang.reflect.ParameterizedType;
     51 import java.lang.reflect.Type;
     52 import java.util.Arrays;
     53 import java.util.LinkedList;
     54 import java.util.List;
     55 import java.util.Set;
     56 
     57 /**
     58  * Class to deal with serializing and deserializing between JSON and Camera2 objects.
     59  */
     60 public class ItsSerializer {
     61     public static final String TAG = ItsSerializer.class.getSimpleName();
     62 
     63     private static class MetadataEntry {
     64         public MetadataEntry(String k, Object v) {
     65             key = k;
     66             value = v;
     67         }
     68         public String key;
     69         public Object value;
     70     }
     71 
     72     @SuppressWarnings("unchecked")
     73     private static Object serializeRational(Rational rat) throws org.json.JSONException {
     74         JSONObject ratObj = new JSONObject();
     75         ratObj.put("numerator", rat.getNumerator());
     76         ratObj.put("denominator", rat.getDenominator());
     77         return ratObj;
     78     }
     79 
     80     @SuppressWarnings("unchecked")
     81     private static Object serializeSize(Size size) throws org.json.JSONException {
     82         JSONObject sizeObj = new JSONObject();
     83         sizeObj.put("width", size.getWidth());
     84         sizeObj.put("height", size.getHeight());
     85         return sizeObj;
     86     }
     87 
     88     @SuppressWarnings("unchecked")
     89     private static Object serializeSizeF(SizeF size) throws org.json.JSONException {
     90         JSONObject sizeObj = new JSONObject();
     91         sizeObj.put("width", size.getWidth());
     92         sizeObj.put("height", size.getHeight());
     93         return sizeObj;
     94     }
     95 
     96     @SuppressWarnings("unchecked")
     97     private static Object serializeRect(Rect rect) throws org.json.JSONException {
     98         JSONObject rectObj = new JSONObject();
     99         rectObj.put("left", rect.left);
    100         rectObj.put("right", rect.right);
    101         rectObj.put("top", rect.top);
    102         rectObj.put("bottom", rect.bottom);
    103         return rectObj;
    104     }
    105 
    106     private static Object serializePoint(Point point) throws org.json.JSONException {
    107         JSONObject pointObj = new JSONObject();
    108         pointObj.put("x", point.x);
    109         pointObj.put("y", point.y);
    110         return pointObj;
    111     }
    112 
    113     @SuppressWarnings("unchecked")
    114     private static Object serializeFace(Face face)
    115             throws org.json.JSONException {
    116         JSONObject faceObj = new JSONObject();
    117         faceObj.put("bounds", serializeRect(face.getBounds()));
    118         faceObj.put("score", face.getScore());
    119         faceObj.put("id", face.getId());
    120         if (face.getLeftEyePosition() != null) {
    121             faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
    122         }
    123         if (face.getRightEyePosition() != null) {
    124             faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
    125         }
    126         if (face.getMouthPosition() != null) {
    127             faceObj.put("mouth", serializePoint(face.getMouthPosition()));
    128         }
    129         return faceObj;
    130     }
    131 
    132     @SuppressWarnings("unchecked")
    133     private static Object serializeStreamConfigurationMap(
    134             StreamConfigurationMap map)
    135             throws org.json.JSONException {
    136         // TODO: Serialize the rest of the StreamConfigurationMap fields.
    137         JSONObject mapObj = new JSONObject();
    138         JSONArray cfgArray = new JSONArray();
    139         int fmts[] = map.getOutputFormats();
    140         if (fmts != null) {
    141             for (int fi = 0; fi < Array.getLength(fmts); fi++) {
    142                 Size sizes[] = map.getOutputSizes(fmts[fi]);
    143                 if (sizes != null) {
    144                     for (int si = 0; si < Array.getLength(sizes); si++) {
    145                         JSONObject obj = new JSONObject();
    146                         obj.put("format", fmts[fi]);
    147                         obj.put("width",sizes[si].getWidth());
    148                         obj.put("height", sizes[si].getHeight());
    149                         obj.put("input", false);
    150                         obj.put("minFrameDuration",
    151                                 map.getOutputMinFrameDuration(fmts[fi],sizes[si]));
    152                         cfgArray.put(obj);
    153                     }
    154                 }
    155                 sizes = map.getHighResolutionOutputSizes(fmts[fi]);
    156                 if (sizes != null) {
    157                     for (int si = 0; si < Array.getLength(sizes); si++) {
    158                         JSONObject obj = new JSONObject();
    159                         obj.put("format", fmts[fi]);
    160                         obj.put("width",sizes[si].getWidth());
    161                         obj.put("height", sizes[si].getHeight());
    162                         obj.put("input", false);
    163                         obj.put("minFrameDuration",
    164                                 map.getOutputMinFrameDuration(fmts[fi],sizes[si]));
    165                         cfgArray.put(obj);
    166                     }
    167                 }
    168             }
    169         }
    170         mapObj.put("availableStreamConfigurations", cfgArray);
    171         return mapObj;
    172     }
    173 
    174     @SuppressWarnings("unchecked")
    175     private static Object serializeMeteringRectangle(MeteringRectangle rect)
    176             throws org.json.JSONException {
    177         JSONObject rectObj = new JSONObject();
    178         rectObj.put("x", rect.getX());
    179         rectObj.put("y", rect.getY());
    180         rectObj.put("width", rect.getWidth());
    181         rectObj.put("height", rect.getHeight());
    182         rectObj.put("weight", rect.getMeteringWeight());
    183         return rectObj;
    184     }
    185 
    186     @SuppressWarnings("unchecked")
    187     private static Object serializePair(Pair pair)
    188             throws org.json.JSONException {
    189         JSONArray pairObj = new JSONArray();
    190         pairObj.put(pair.first);
    191         pairObj.put(pair.second);
    192         return pairObj;
    193     }
    194 
    195     @SuppressWarnings("unchecked")
    196     private static Object serializeRange(Range range)
    197             throws org.json.JSONException {
    198         JSONArray rangeObj = new JSONArray();
    199         rangeObj.put(range.getLower());
    200         rangeObj.put(range.getUpper());
    201         return rangeObj;
    202     }
    203 
    204     @SuppressWarnings("unchecked")
    205     private static Object serializeColorSpaceTransform(ColorSpaceTransform xform)
    206             throws org.json.JSONException {
    207         JSONArray xformObj = new JSONArray();
    208         for (int row = 0; row < 3; row++) {
    209             for (int col = 0; col < 3; col++) {
    210                 xformObj.put(serializeRational(xform.getElement(col,row)));
    211             }
    212         }
    213         return xformObj;
    214     }
    215 
    216     @SuppressWarnings("unchecked")
    217     private static Object serializeTonemapCurve(TonemapCurve curve)
    218             throws org.json.JSONException {
    219         JSONObject curveObj = new JSONObject();
    220         String names[] = {"red", "green", "blue"};
    221         for (int ch = 0; ch < 3; ch++) {
    222             JSONArray curveArr = new JSONArray();
    223             int len = curve.getPointCount(ch);
    224             for (int i = 0; i < len; i++) {
    225                 curveArr.put(curve.getPoint(ch,i).x);
    226                 curveArr.put(curve.getPoint(ch,i).y);
    227             }
    228             curveObj.put(names[ch], curveArr);
    229         }
    230         return curveObj;
    231     }
    232 
    233     @SuppressWarnings("unchecked")
    234     private static Object serializeRggbChannelVector(RggbChannelVector vec)
    235             throws org.json.JSONException {
    236         JSONArray vecObj = new JSONArray();
    237         vecObj.put(vec.getRed());
    238         vecObj.put(vec.getGreenEven());
    239         vecObj.put(vec.getGreenOdd());
    240         vecObj.put(vec.getBlue());
    241         return vecObj;
    242     }
    243 
    244     @SuppressWarnings("unchecked")
    245     private static Object serializeBlackLevelPattern(BlackLevelPattern pat)
    246             throws org.json.JSONException {
    247         int patVals[] = new int[4];
    248         pat.copyTo(patVals, 0);
    249         JSONArray patObj = new JSONArray();
    250         patObj.put(patVals[0]);
    251         patObj.put(patVals[1]);
    252         patObj.put(patVals[2]);
    253         patObj.put(patVals[3]);
    254         return patObj;
    255     }
    256 
    257     @SuppressWarnings("unchecked")
    258     private static Object serializeLocation(Location loc)
    259             throws org.json.JSONException {
    260         return loc.toString();
    261     }
    262 
    263     @SuppressWarnings("unchecked")
    264     private static Object serializeLensShadingMap(LensShadingMap map)
    265             throws org.json.JSONException {
    266         JSONObject mapObj = new JSONObject();
    267         JSONArray mapArr = new JSONArray();
    268         for (int row = 0; row < map.getRowCount(); row++) {
    269             for (int col = 0; col < map.getColumnCount(); col++) {
    270                 for (int ch = 0; ch < 4; ch++) {
    271                     mapArr.put(map.getGainFactor(ch, col, row));
    272                 }
    273             }
    274         }
    275         mapObj.put("width", map.getColumnCount());
    276         mapObj.put("height", map.getRowCount());
    277         mapObj.put("map", mapArr);
    278         return mapObj;
    279     }
    280 
    281     private static String getKeyName(Object keyObj) throws ItsException {
    282         if (keyObj.getClass() == CaptureResult.Key.class
    283                 || keyObj.getClass() == TotalCaptureResult.class) {
    284             return ((CaptureResult.Key)keyObj).getName();
    285         } else if (keyObj.getClass() == CaptureRequest.Key.class) {
    286             return ((CaptureRequest.Key)keyObj).getName();
    287         } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
    288             return ((CameraCharacteristics.Key)keyObj).getName();
    289         }
    290         throw new ItsException("Invalid key object");
    291     }
    292 
    293     private static Object getKeyValue(CameraMetadata md, Object keyObj) throws ItsException {
    294         if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
    295             return ((CaptureResult)md).get((CaptureResult.Key)keyObj);
    296         } else if (md.getClass() == CaptureRequest.class) {
    297             return ((CaptureRequest)md).get((CaptureRequest.Key)keyObj);
    298         } else if (md.getClass() == CameraCharacteristics.class) {
    299             return ((CameraCharacteristics)md).get((CameraCharacteristics.Key)keyObj);
    300         }
    301         throw new ItsException("Invalid key object");
    302     }
    303 
    304     @SuppressWarnings("unchecked")
    305     private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md)
    306             throws ItsException {
    307         String keyName = getKeyName(keyObj);
    308 
    309         try {
    310             Object keyValue = getKeyValue(md, keyObj);
    311             if (keyValue == null) {
    312                 return new MetadataEntry(keyName, JSONObject.NULL);
    313             } else if (keyType == Float.class) {
    314                 // The JSON serializer doesn't handle floating point NaN or Inf.
    315                 if (((Float)keyValue).isInfinite() || ((Float)keyValue).isNaN()) {
    316                     Logt.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
    317                     return null;
    318                 }
    319                 return new MetadataEntry(keyName, keyValue);
    320             } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
    321                        keyType == Boolean.class || keyType == String.class) {
    322                 return new MetadataEntry(keyName, keyValue);
    323             } else if (keyType == Rational.class) {
    324                 return new MetadataEntry(keyName, serializeRational((Rational)keyValue));
    325             } else if (keyType == Size.class) {
    326                 return new MetadataEntry(keyName, serializeSize((Size)keyValue));
    327             } else if (keyType == SizeF.class) {
    328                 return new MetadataEntry(keyName, serializeSizeF((SizeF)keyValue));
    329             } else if (keyType == Rect.class) {
    330                 return new MetadataEntry(keyName, serializeRect((Rect)keyValue));
    331             } else if (keyType == Face.class) {
    332                 return new MetadataEntry(keyName, serializeFace((Face)keyValue));
    333             } else if (keyType == StreamConfigurationMap.class) {
    334                 return new MetadataEntry(keyName,
    335                         serializeStreamConfigurationMap((StreamConfigurationMap)keyValue));
    336             } else if (keyType instanceof ParameterizedType &&
    337                     ((ParameterizedType)keyType).getRawType() == Range.class) {
    338                 return new MetadataEntry(keyName, serializeRange((Range)keyValue));
    339             } else if (keyType == ColorSpaceTransform.class) {
    340                 return new MetadataEntry(keyName,
    341                         serializeColorSpaceTransform((ColorSpaceTransform)keyValue));
    342             } else if (keyType == MeteringRectangle.class) {
    343                 return new MetadataEntry(keyName,
    344                         serializeMeteringRectangle((MeteringRectangle)keyValue));
    345             } else if (keyType == Location.class) {
    346                 return new MetadataEntry(keyName,
    347                         serializeLocation((Location)keyValue));
    348             } else if (keyType == RggbChannelVector.class) {
    349                 return new MetadataEntry(keyName,
    350                         serializeRggbChannelVector((RggbChannelVector)keyValue));
    351             } else if (keyType == BlackLevelPattern.class) {
    352                 return new MetadataEntry(keyName,
    353                         serializeBlackLevelPattern((BlackLevelPattern)keyValue));
    354             } else if (keyType == TonemapCurve.class) {
    355                 return new MetadataEntry(keyName,
    356                         serializeTonemapCurve((TonemapCurve)keyValue));
    357             } else if (keyType == Point.class) {
    358                 return new MetadataEntry(keyName,
    359                         serializePoint((Point)keyValue));
    360             } else if (keyType == LensShadingMap.class) {
    361                 return new MetadataEntry(keyName,
    362                         serializeLensShadingMap((LensShadingMap)keyValue));
    363             } else {
    364                 Logt.w(TAG, String.format("Serializing unsupported key type: " + keyType));
    365                 return null;
    366             }
    367         } catch (org.json.JSONException e) {
    368             throw new ItsException("JSON error for key: " + keyName + ": ", e);
    369         }
    370     }
    371 
    372     @SuppressWarnings("unchecked")
    373     private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj, CameraMetadata md)
    374             throws ItsException {
    375         String keyName = getKeyName(keyObj);
    376         try {
    377             Object keyValue = getKeyValue(md, keyObj);
    378             if (keyValue == null) {
    379                 return null;
    380             }
    381             int arrayLen = Array.getLength(keyValue);
    382             Type elmtType = ((GenericArrayType)keyType).getGenericComponentType();
    383             if (elmtType == int.class  || elmtType == float.class || elmtType == byte.class ||
    384                 elmtType == long.class || elmtType == double.class || elmtType == boolean.class) {
    385                 return new MetadataEntry(keyName, new JSONArray(keyValue));
    386             } else if (elmtType == Rational.class) {
    387                 JSONArray jsonArray = new JSONArray();
    388                 for (int i = 0; i < arrayLen; i++) {
    389                     jsonArray.put(serializeRational((Rational)Array.get(keyValue,i)));
    390                 }
    391                 return new MetadataEntry(keyName, jsonArray);
    392             } else if (elmtType == Size.class) {
    393                 JSONArray jsonArray = new JSONArray();
    394                 for (int i = 0; i < arrayLen; i++) {
    395                     jsonArray.put(serializeSize((Size)Array.get(keyValue,i)));
    396                 }
    397                 return new MetadataEntry(keyName, jsonArray);
    398             } else if (elmtType == Rect.class) {
    399                 JSONArray jsonArray = new JSONArray();
    400                 for (int i = 0; i < arrayLen; i++) {
    401                     jsonArray.put(serializeRect((Rect)Array.get(keyValue,i)));
    402                 }
    403                 return new MetadataEntry(keyName, jsonArray);
    404             } else if (elmtType == Face.class) {
    405                 JSONArray jsonArray = new JSONArray();
    406                 for (int i = 0; i < arrayLen; i++) {
    407                     jsonArray.put(serializeFace((Face)Array.get(keyValue, i)));
    408                 }
    409                 return new MetadataEntry(keyName, jsonArray);
    410             } else if (elmtType == StreamConfigurationMap.class) {
    411                 JSONArray jsonArray = new JSONArray();
    412                 for (int i = 0; i < arrayLen; i++) {
    413                     jsonArray.put(serializeStreamConfigurationMap(
    414                             (StreamConfigurationMap)Array.get(keyValue,i)));
    415                 }
    416                 return new MetadataEntry(keyName, jsonArray);
    417             } else if (elmtType instanceof ParameterizedType &&
    418                     ((ParameterizedType)elmtType).getRawType() == Range.class) {
    419                 JSONArray jsonArray = new JSONArray();
    420                 for (int i = 0; i < arrayLen; i++) {
    421                     jsonArray.put(serializeRange((Range)Array.get(keyValue,i)));
    422                 }
    423                 return new MetadataEntry(keyName, jsonArray);
    424             } else if (elmtType instanceof ParameterizedType &&
    425                     ((ParameterizedType)elmtType).getRawType() == Pair.class) {
    426                 JSONArray jsonArray = new JSONArray();
    427                 for (int i = 0; i < arrayLen; i++) {
    428                     jsonArray.put(serializePair((Pair)Array.get(keyValue,i)));
    429                 }
    430                 return new MetadataEntry(keyName, jsonArray);
    431             } else if (elmtType == MeteringRectangle.class) {
    432                 JSONArray jsonArray = new JSONArray();
    433                 for (int i = 0; i < arrayLen; i++) {
    434                     jsonArray.put(serializeMeteringRectangle(
    435                             (MeteringRectangle)Array.get(keyValue,i)));
    436                 }
    437                 return new MetadataEntry(keyName, jsonArray);
    438             } else if (elmtType == Location.class) {
    439                 JSONArray jsonArray = new JSONArray();
    440                 for (int i = 0; i < arrayLen; i++) {
    441                     jsonArray.put(serializeLocation((Location)Array.get(keyValue,i)));
    442                 }
    443                 return new MetadataEntry(keyName, jsonArray);
    444             } else if (elmtType == RggbChannelVector.class) {
    445                 JSONArray jsonArray = new JSONArray();
    446                 for (int i = 0; i < arrayLen; i++) {
    447                     jsonArray.put(serializeRggbChannelVector(
    448                             (RggbChannelVector)Array.get(keyValue,i)));
    449                 }
    450                 return new MetadataEntry(keyName, jsonArray);
    451             } else if (elmtType == BlackLevelPattern.class) {
    452                 JSONArray jsonArray = new JSONArray();
    453                 for (int i = 0; i < arrayLen; i++) {
    454                     jsonArray.put(serializeBlackLevelPattern(
    455                             (BlackLevelPattern)Array.get(keyValue,i)));
    456                 }
    457                 return new MetadataEntry(keyName, jsonArray);
    458             } else if (elmtType == Point.class) {
    459                 JSONArray jsonArray = new JSONArray();
    460                 for (int i = 0; i < arrayLen; i++) {
    461                     jsonArray.put(serializePoint((Point)Array.get(keyValue,i)));
    462                 }
    463                 return new MetadataEntry(keyName, jsonArray);
    464             } else {
    465                 Logt.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
    466                 return null;
    467             }
    468         } catch (org.json.JSONException e) {
    469             throw new ItsException("JSON error for key: " + keyName + ": ", e);
    470         }
    471     }
    472 
    473     @SuppressWarnings("unchecked")
    474     public static JSONObject serialize(CameraMetadata md)
    475             throws ItsException {
    476         JSONObject jsonObj = new JSONObject();
    477         Field[] allFields = md.getClass().getDeclaredFields();
    478         if (md.getClass() == TotalCaptureResult.class) {
    479             allFields = CaptureResult.class.getDeclaredFields();
    480         }
    481         if (md.getClass() == CameraCharacteristics.class) {
    482             // Special handling for information not stored in metadata keys
    483             CameraCharacteristics chars = (CameraCharacteristics) md;
    484             List<CameraCharacteristics.Key<?>> charsKeys = chars.getKeys();
    485             List<CaptureRequest.Key<?>> requestKeys = chars.getAvailableCaptureRequestKeys();
    486             Set<String> physicalCamIds = chars.getPhysicalCameraIds();
    487 
    488             try {
    489                 JSONArray charKeysArr = new JSONArray();
    490                 for (CameraCharacteristics.Key<?> k : charsKeys) {
    491                     charKeysArr.put(k.getName());
    492                 }
    493                 JSONArray reqKeysArr = new JSONArray();
    494                 for (CaptureRequest.Key<?> k : requestKeys) {
    495                     reqKeysArr.put(k.getName());
    496                 }
    497                 // Avoid using the hidden metadata key name here to prevent confliction
    498                 jsonObj.put("camera.characteristics.keys", charKeysArr);
    499                 jsonObj.put("camera.characteristics.requestKeys", reqKeysArr);
    500 
    501                 if (!physicalCamIds.isEmpty()) {
    502                     JSONArray physCamIdsArr = new JSONArray();
    503                     for (String id : physicalCamIds) {
    504                         physCamIdsArr.put(id);
    505                     }
    506                     jsonObj.put("camera.characteristics.physicalCamIds", physCamIdsArr);
    507                 }
    508             } catch (org.json.JSONException e) {
    509                 throw new ItsException("JSON error for CameraCharacteristics:", e);
    510             }
    511         }
    512         for (Field field : allFields) {
    513             if (Modifier.isPublic(field.getModifiers()) &&
    514                     Modifier.isStatic(field.getModifiers()) &&
    515                     (field.getType() == CaptureRequest.Key.class
    516                       || field.getType() == CaptureResult.Key.class
    517                       || field.getType() == TotalCaptureResult.Key.class
    518                       || field.getType() == CameraCharacteristics.Key.class) &&
    519                     field.getGenericType() instanceof ParameterizedType) {
    520                 ParameterizedType paramType = (ParameterizedType)field.getGenericType();
    521                 Type[] argTypes = paramType.getActualTypeArguments();
    522                 if (argTypes.length > 0) {
    523                     try {
    524                         Type keyType = argTypes[0];
    525                         Object keyObj = field.get(md);
    526                         MetadataEntry entry;
    527                         if (keyType instanceof GenericArrayType) {
    528                             entry = serializeArrayEntry(keyType, keyObj, md);
    529                         } else {
    530                             entry = serializeEntry(keyType, keyObj, md);
    531                         }
    532 
    533                         // TODO: Figure this weird case out.
    534                         // There is a weird case where the entry is non-null but the toString
    535                         // of the entry is null, and if this happens, the null-ness spreads like
    536                         // a virus and makes the whole JSON object null from the top level down.
    537                         // Not sure if it's a bug in the library or I'm just not using it right.
    538                         // Workaround by checking for this case explicitly and not adding the
    539                         // value to the jsonObj when it is detected.
    540                         if (entry != null && entry.key != null && entry.value != null
    541                                           && entry.value.toString() == null) {
    542                             Logt.w(TAG, "Error encountered serializing value for key: " + entry.key);
    543                         } else if (entry != null) {
    544                             jsonObj.put(entry.key, entry.value);
    545                         } else {
    546                             // Ignore.
    547                         }
    548                     } catch (IllegalAccessException e) {
    549                         throw new ItsException(
    550                                 "Access error for field: " + field + ": ", e);
    551                     } catch (org.json.JSONException e) {
    552                         throw new ItsException(
    553                                 "JSON error for field: " + field + ": ", e);
    554                     }
    555                 }
    556             }
    557         }
    558         return jsonObj;
    559     }
    560 
    561     @SuppressWarnings("unchecked")
    562     public static CaptureRequest.Builder deserialize(CaptureRequest.Builder mdDefault,
    563             JSONObject jsonReq) throws ItsException {
    564         try {
    565             Logt.i(TAG, "Parsing JSON capture request ...");
    566 
    567             // Iterate over the CaptureRequest reflected fields.
    568             CaptureRequest.Builder md = mdDefault;
    569             Field[] allFields = CaptureRequest.class.getDeclaredFields();
    570             for (Field field : allFields) {
    571                 if (Modifier.isPublic(field.getModifiers()) &&
    572                         Modifier.isStatic(field.getModifiers()) &&
    573                         field.getType() == CaptureRequest.Key.class &&
    574                         field.getGenericType() instanceof ParameterizedType) {
    575                     ParameterizedType paramType = (ParameterizedType)field.getGenericType();
    576                     Type[] argTypes = paramType.getActualTypeArguments();
    577                     if (argTypes.length > 0) {
    578                         CaptureRequest.Key key = (CaptureRequest.Key)field.get(md);
    579                         String keyName = key.getName();
    580                         Type keyType = argTypes[0];
    581 
    582                         // For each reflected CaptureRequest entry, look inside the JSON object
    583                         // to see if it is being set. If it is found, remove the key from the
    584                         // JSON object. After this process, there should be no keys left in the
    585                         // JSON (otherwise an invalid key was specified).
    586 
    587                         if (jsonReq.has(keyName) && !jsonReq.isNull(keyName)) {
    588                             if (keyType instanceof GenericArrayType) {
    589                                 Type elmtType =
    590                                         ((GenericArrayType)keyType).getGenericComponentType();
    591                                 JSONArray ja = jsonReq.getJSONArray(keyName);
    592                                 Object val[] = new Object[ja.length()];
    593                                 for (int i = 0; i < ja.length(); i++) {
    594                                     if (elmtType == int.class) {
    595                                         Array.set(val, i, ja.getInt(i));
    596                                     } else if (elmtType == byte.class) {
    597                                         Array.set(val, i, (byte)ja.getInt(i));
    598                                     } else if (elmtType == float.class) {
    599                                         Array.set(val, i, (float)ja.getDouble(i));
    600                                     } else if (elmtType == long.class) {
    601                                         Array.set(val, i, ja.getLong(i));
    602                                     } else if (elmtType == double.class) {
    603                                         Array.set(val, i, ja.getDouble(i));
    604                                     } else if (elmtType == boolean.class) {
    605                                         Array.set(val, i, ja.getBoolean(i));
    606                                     } else if (elmtType == String.class) {
    607                                         Array.set(val, i, ja.getString(i));
    608                                     } else if (elmtType == Size.class){
    609                                         JSONObject obj = ja.getJSONObject(i);
    610                                         Array.set(val, i, new Size(
    611                                                 obj.getInt("width"), obj.getInt("height")));
    612                                     } else if (elmtType == Rect.class) {
    613                                         JSONObject obj = ja.getJSONObject(i);
    614                                         Array.set(val, i, new Rect(
    615                                                 obj.getInt("left"), obj.getInt("top"),
    616                                                 obj.getInt("bottom"), obj.getInt("right")));
    617                                     } else if (elmtType == Rational.class) {
    618                                         JSONObject obj = ja.getJSONObject(i);
    619                                         Array.set(val, i, new Rational(
    620                                                 obj.getInt("numerator"),
    621                                                 obj.getInt("denominator")));
    622                                     } else if (elmtType == RggbChannelVector.class) {
    623                                         JSONArray arr = ja.getJSONArray(i);
    624                                         Array.set(val, i, new RggbChannelVector(
    625                                                 (float)arr.getDouble(0),
    626                                                 (float)arr.getDouble(1),
    627                                                 (float)arr.getDouble(2),
    628                                                 (float)arr.getDouble(3)));
    629                                     } else if (elmtType == ColorSpaceTransform.class) {
    630                                         JSONArray arr = ja.getJSONArray(i);
    631                                         Rational xform[] = new Rational[9];
    632                                         for (int j = 0; j < 9; j++) {
    633                                             xform[j] = new Rational(
    634                                                     arr.getJSONObject(j).getInt("numerator"),
    635                                                     arr.getJSONObject(j).getInt("denominator"));
    636                                         }
    637                                         Array.set(val, i, new ColorSpaceTransform(xform));
    638                                     } else if (elmtType == MeteringRectangle.class) {
    639                                         JSONObject obj = ja.getJSONObject(i);
    640                                         Array.set(val, i, new MeteringRectangle(
    641                                                 obj.getInt("x"),
    642                                                 obj.getInt("y"),
    643                                                 obj.getInt("width"),
    644                                                 obj.getInt("height"),
    645                                                 obj.getInt("weight")));
    646                                     } else {
    647                                         throw new ItsException(
    648                                                 "Failed to parse key from JSON: " + keyName);
    649                                     }
    650                                 }
    651                                 if (val != null) {
    652                                     Logt.i(TAG, "Set: "+keyName+" -> "+Arrays.toString(val));
    653                                     md.set(key, val);
    654                                     jsonReq.remove(keyName);
    655                                 }
    656                             } else {
    657                                 Object val = null;
    658                                 if (keyType == Integer.class) {
    659                                     val = jsonReq.getInt(keyName);
    660                                 } else if (keyType == Byte.class) {
    661                                     val = (byte)jsonReq.getInt(keyName);
    662                                 } else if (keyType == Double.class) {
    663                                     val = jsonReq.getDouble(keyName);
    664                                 } else if (keyType == Long.class) {
    665                                     val = jsonReq.getLong(keyName);
    666                                 } else if (keyType == Float.class) {
    667                                     val = (float)jsonReq.getDouble(keyName);
    668                                 } else if (keyType == Boolean.class) {
    669                                     val = jsonReq.getBoolean(keyName);
    670                                 } else if (keyType == String.class) {
    671                                     val = jsonReq.getString(keyName);
    672                                 } else if (keyType == Size.class) {
    673                                     JSONObject obj = jsonReq.getJSONObject(keyName);
    674                                     val = new Size(
    675                                             obj.getInt("width"), obj.getInt("height"));
    676                                 } else if (keyType == Rect.class) {
    677                                     JSONObject obj = jsonReq.getJSONObject(keyName);
    678                                     val = new Rect(
    679                                             obj.getInt("left"), obj.getInt("top"),
    680                                             obj.getInt("right"), obj.getInt("bottom"));
    681                                 } else if (keyType == Rational.class) {
    682                                     JSONObject obj = jsonReq.getJSONObject(keyName);
    683                                     val = new Rational(obj.getInt("numerator"),
    684                                                        obj.getInt("denominator"));
    685                                 } else if (keyType == RggbChannelVector.class) {
    686                                     JSONObject obj = jsonReq.optJSONObject(keyName);
    687                                     JSONArray arr = jsonReq.optJSONArray(keyName);
    688                                     if (arr != null) {
    689                                         val = new RggbChannelVector(
    690                                                 (float)arr.getDouble(0),
    691                                                 (float)arr.getDouble(1),
    692                                                 (float)arr.getDouble(2),
    693                                                 (float)arr.getDouble(3));
    694                                     } else if (obj != null) {
    695                                         val = new RggbChannelVector(
    696                                                 (float)obj.getDouble("red"),
    697                                                 (float)obj.getDouble("greenEven"),
    698                                                 (float)obj.getDouble("greenOdd"),
    699                                                 (float)obj.getDouble("blue"));
    700                                     } else {
    701                                         throw new ItsException("Invalid RggbChannelVector object");
    702                                     }
    703                                 } else if (keyType == ColorSpaceTransform.class) {
    704                                     JSONArray arr = jsonReq.getJSONArray(keyName);
    705                                     Rational a[] = new Rational[9];
    706                                     for (int i = 0; i < 9; i++) {
    707                                         a[i] = new Rational(
    708                                                 arr.getJSONObject(i).getInt("numerator"),
    709                                                 arr.getJSONObject(i).getInt("denominator"));
    710                                     }
    711                                     val = new ColorSpaceTransform(a);
    712                                 } else if (keyType == TonemapCurve.class) {
    713                                     JSONObject obj = jsonReq.optJSONObject(keyName);
    714                                     String names[] = {"red", "green", "blue"};
    715                                     float[][] curves = new float[3][];
    716                                     for (int ch = 0; ch < 3; ch++) {
    717                                         JSONArray ja = obj.getJSONArray(names[ch]);
    718                                         curves[ch] = new float[ja.length()];
    719                                         for (int i = 0; i < ja.length(); i++) {
    720                                             Array.set(curves[ch], i, (float)ja.getDouble(i));
    721                                         }
    722                                     }
    723                                     val = new TonemapCurve(curves[0], curves[1], curves[2]);
    724                                 } else if (keyType instanceof ParameterizedType &&
    725                                         ((ParameterizedType)keyType).getRawType() == Range.class &&
    726                                         ((ParameterizedType)keyType).getActualTypeArguments().length == 1 &&
    727                                         ((ParameterizedType)keyType).getActualTypeArguments()[0] == Integer.class) {
    728                                     JSONArray arr = jsonReq.getJSONArray(keyName);
    729                                     val = new Range<Integer>(arr.getInt(0), arr.getInt(1));
    730                                 } else {
    731                                     throw new ItsException(
    732                                             "Failed to parse key from JSON: " +
    733                                             keyName + ", " + keyType);
    734                                 }
    735                                 if (val != null) {
    736                                     Logt.i(TAG, "Set: " + keyName + " -> " + val);
    737                                     md.set(key ,val);
    738                                     jsonReq.remove(keyName);
    739                                 }
    740                             }
    741                         }
    742                     }
    743                 }
    744             }
    745 
    746             // Ensure that there were no invalid keys in the JSON request object.
    747             if (jsonReq.length() != 0) {
    748                 throw new ItsException("Invalid JSON key(s): " + jsonReq.toString());
    749             }
    750 
    751             Logt.i(TAG, "Parsing JSON capture request completed");
    752             return md;
    753         } catch (java.lang.IllegalAccessException e) {
    754             throw new ItsException("Access error: ", e);
    755         } catch (org.json.JSONException e) {
    756             throw new ItsException("JSON error: ", e);
    757         }
    758     }
    759 
    760     @SuppressWarnings("unchecked")
    761     public static List<CaptureRequest.Builder> deserializeRequestList(
    762             CameraDevice device, JSONObject jsonObjTop, String requestKey)
    763             throws ItsException {
    764         try {
    765             List<CaptureRequest.Builder> requests = null;
    766             JSONArray jsonReqs = jsonObjTop.getJSONArray(requestKey);
    767             requests = new LinkedList<CaptureRequest.Builder>();
    768             for (int i = 0; i < jsonReqs.length(); i++) {
    769                 CaptureRequest.Builder templateReq = device.createCaptureRequest(
    770                         CameraDevice.TEMPLATE_STILL_CAPTURE);
    771                 requests.add(
    772                     deserialize(templateReq, jsonReqs.getJSONObject(i)));
    773             }
    774             return requests;
    775         } catch (org.json.JSONException e) {
    776             throw new ItsException("JSON error: ", e);
    777         } catch (android.hardware.camera2.CameraAccessException e) {
    778             throw new ItsException("Access error: ", e);
    779         }
    780     }
    781 }
    782