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.CaptureRequest;
25 import android.hardware.camera2.CaptureResult;
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.LensIntrinsicsSample;
31 import android.hardware.camera2.params.LensShadingMap;
32 import android.hardware.camera2.params.MeteringRectangle;
33 import android.hardware.camera2.params.RggbChannelVector;
34 import android.hardware.camera2.params.StreamConfigurationMap;
35 import android.hardware.camera2.params.TonemapCurve;
36 import android.location.Location;
37 import android.util.Pair;
38 import android.util.Range;
39 import android.util.Rational;
40 import android.util.Size;
41 import android.util.SizeF;
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 {
MetadataEntry(String k, Object v)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")
serializeRational(Rational rat)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")
serializeSize(Size size)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")
serializeSizeF(SizeF size)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")
serializeRect(Rect rect)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 
serializePoint(Point point)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")
serializeFace(Face face)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")
serializeStreamConfigurationMap( StreamConfigurationMap map)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")
serializeMeteringRectangle(MeteringRectangle rect)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")
serializePair(Pair pair)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")
serializeRange(Range range)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")
serializeColorSpaceTransform(ColorSpaceTransform xform)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")
serializeTonemapCurve(TonemapCurve curve)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")
serializeRggbChannelVector(RggbChannelVector vec)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")
serializeBlackLevelPattern(BlackLevelPattern pat)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")
serializeLocation(Location loc)258     private static Object serializeLocation(Location loc)
259             throws org.json.JSONException {
260         return loc.toString();
261     }
262 
263     @SuppressWarnings("unchecked")
serializeLensShadingMap(LensShadingMap map)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     @SuppressWarnings("unchecked")
serializeIntrinsicsSamples(LensIntrinsicsSample [] samples)282     private static Object serializeIntrinsicsSamples(LensIntrinsicsSample [] samples)
283             throws org.json.JSONException {
284         JSONArray top = new JSONArray();
285         for (LensIntrinsicsSample sample : samples) {
286             JSONObject jSample = new JSONObject();
287             jSample.put("timestamp", sample.getTimestampNanos());
288             JSONArray lensIntrinsics = new JSONArray();
289             for (float intrinsic : sample.getLensIntrinsics()) {
290                 lensIntrinsics.put(intrinsic);
291             }
292             jSample.put("lensIntrinsics", lensIntrinsics);
293             top.put(jSample);
294         }
295         return top;
296     }
getKeyName(Object keyObj)297     private static String getKeyName(Object keyObj) throws ItsException {
298         if (keyObj.getClass() == CaptureResult.Key.class
299                 || keyObj.getClass() == TotalCaptureResult.class) {
300             return ((CaptureResult.Key)keyObj).getName();
301         } else if (keyObj.getClass() == CaptureRequest.Key.class) {
302             return ((CaptureRequest.Key)keyObj).getName();
303         } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
304             return ((CameraCharacteristics.Key)keyObj).getName();
305         }
306         throw new ItsException("Invalid key object");
307     }
308 
getKeyValue(CameraMetadata md, Object keyObj)309     private static Object getKeyValue(CameraMetadata md, Object keyObj) throws ItsException {
310         if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
311             return ((CaptureResult)md).get((CaptureResult.Key)keyObj);
312         } else if (md.getClass() == CaptureRequest.class) {
313             return ((CaptureRequest)md).get((CaptureRequest.Key)keyObj);
314         } else if (md.getClass() == CameraCharacteristics.class) {
315             return ((CameraCharacteristics)md).get((CameraCharacteristics.Key)keyObj);
316         }
317         throw new ItsException("Invalid key object");
318     }
319 
serializeEntry(Type keyType, Object keyObj, CameraMetadata md)320     private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md)
321             throws ItsException {
322         return serializeEntry(keyType, keyObj, getKeyValue(md, keyObj));
323     }
324 
325     @SuppressWarnings("unchecked")
serializeEntry(Type keyType, Object keyObj, Object keyValue)326     private static MetadataEntry serializeEntry(Type keyType, Object keyObj, Object keyValue)
327             throws ItsException {
328         String keyName = getKeyName(keyObj);
329 
330         try {
331             if (keyValue == null) {
332                 return new MetadataEntry(keyName, JSONObject.NULL);
333             } else if (keyType == Float.class) {
334                 // The JSON serializer doesn't handle floating point NaN or Inf.
335                 if (((Float)keyValue).isInfinite() || ((Float)keyValue).isNaN()) {
336                     Logt.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
337                     return null;
338                 }
339                 return new MetadataEntry(keyName, keyValue);
340             } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
341                        keyType == Boolean.class || keyType == String.class) {
342                 return new MetadataEntry(keyName, keyValue);
343             } else if (keyType == Rational.class) {
344                 return new MetadataEntry(keyName, serializeRational((Rational)keyValue));
345             } else if (keyType == Size.class) {
346                 return new MetadataEntry(keyName, serializeSize((Size)keyValue));
347             } else if (keyType == SizeF.class) {
348                 return new MetadataEntry(keyName, serializeSizeF((SizeF)keyValue));
349             } else if (keyType == Rect.class) {
350                 return new MetadataEntry(keyName, serializeRect((Rect)keyValue));
351             } else if (keyType == Face.class) {
352                 return new MetadataEntry(keyName, serializeFace((Face)keyValue));
353             } else if (keyType == StreamConfigurationMap.class) {
354                 return new MetadataEntry(keyName,
355                         serializeStreamConfigurationMap((StreamConfigurationMap)keyValue));
356             } else if (keyType instanceof ParameterizedType &&
357                     ((ParameterizedType)keyType).getRawType() == Range.class) {
358                 return new MetadataEntry(keyName, serializeRange((Range)keyValue));
359             } else if (keyType == ColorSpaceTransform.class) {
360                 return new MetadataEntry(keyName,
361                         serializeColorSpaceTransform((ColorSpaceTransform)keyValue));
362             } else if (keyType == MeteringRectangle.class) {
363                 return new MetadataEntry(keyName,
364                         serializeMeteringRectangle((MeteringRectangle)keyValue));
365             } else if (keyType == Location.class) {
366                 return new MetadataEntry(keyName,
367                         serializeLocation((Location)keyValue));
368             } else if (keyType == RggbChannelVector.class) {
369                 return new MetadataEntry(keyName,
370                         serializeRggbChannelVector((RggbChannelVector)keyValue));
371             } else if (keyType == BlackLevelPattern.class) {
372                 return new MetadataEntry(keyName,
373                         serializeBlackLevelPattern((BlackLevelPattern)keyValue));
374             } else if (keyType == TonemapCurve.class) {
375                 return new MetadataEntry(keyName,
376                         serializeTonemapCurve((TonemapCurve)keyValue));
377             } else if (keyType == Point.class) {
378                 return new MetadataEntry(keyName,
379                         serializePoint((Point)keyValue));
380             } else if (keyType == LensShadingMap.class) {
381                 return new MetadataEntry(keyName,
382                         serializeLensShadingMap((LensShadingMap)keyValue));
383             } else if (keyValue instanceof LensIntrinsicsSample[]) {
384                 return new MetadataEntry(keyName,
385                         serializeIntrinsicsSamples((LensIntrinsicsSample []) keyValue));
386             } else if (keyValue instanceof float[]) {
387                 return new MetadataEntry(keyName, new JSONArray(keyValue));
388             } else {
389                 Logt.w(TAG, String.format("Serializing unsupported key type: " + keyType));
390                 return null;
391             }
392         } catch (org.json.JSONException e) {
393             throw new ItsException("JSON error for key: " + keyName + ": ", e);
394         }
395     }
396 
serializeArrayEntry(Type keyType, Object keyObj, CameraMetadata md)397     private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj, CameraMetadata md)
398             throws ItsException {
399         return serializeArrayEntry(keyType, keyObj, getKeyValue(md, keyObj));
400     }
401 
402     @SuppressWarnings("unchecked")
serializeArrayEntry(Type keyType, Object keyObj, Object keyValue)403     private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj, Object keyValue)
404             throws ItsException {
405         String keyName = getKeyName(keyObj);
406         try {
407             if (keyValue == null) {
408                 return null;
409             }
410             int arrayLen = Array.getLength(keyValue);
411             Type elmtType = ((GenericArrayType)keyType).getGenericComponentType();
412             if (elmtType == int.class  || elmtType == float.class || elmtType == byte.class ||
413                 elmtType == long.class || elmtType == double.class || elmtType == boolean.class) {
414                 return new MetadataEntry(keyName, new JSONArray(keyValue));
415             } else if (elmtType == Rational.class) {
416                 JSONArray jsonArray = new JSONArray();
417                 for (int i = 0; i < arrayLen; i++) {
418                     jsonArray.put(serializeRational((Rational)Array.get(keyValue,i)));
419                 }
420                 return new MetadataEntry(keyName, jsonArray);
421             } else if (elmtType == Size.class) {
422                 JSONArray jsonArray = new JSONArray();
423                 for (int i = 0; i < arrayLen; i++) {
424                     jsonArray.put(serializeSize((Size)Array.get(keyValue,i)));
425                 }
426                 return new MetadataEntry(keyName, jsonArray);
427             } else if (elmtType == Rect.class) {
428                 JSONArray jsonArray = new JSONArray();
429                 for (int i = 0; i < arrayLen; i++) {
430                     jsonArray.put(serializeRect((Rect)Array.get(keyValue,i)));
431                 }
432                 return new MetadataEntry(keyName, jsonArray);
433             } else if (elmtType == Face.class) {
434                 JSONArray jsonArray = new JSONArray();
435                 for (int i = 0; i < arrayLen; i++) {
436                     jsonArray.put(serializeFace((Face)Array.get(keyValue, i)));
437                 }
438                 return new MetadataEntry(keyName, jsonArray);
439             } else if (elmtType == StreamConfigurationMap.class) {
440                 JSONArray jsonArray = new JSONArray();
441                 for (int i = 0; i < arrayLen; i++) {
442                     jsonArray.put(serializeStreamConfigurationMap(
443                             (StreamConfigurationMap)Array.get(keyValue,i)));
444                 }
445                 return new MetadataEntry(keyName, jsonArray);
446             } else if (elmtType instanceof ParameterizedType &&
447                     ((ParameterizedType)elmtType).getRawType() == Range.class) {
448                 JSONArray jsonArray = new JSONArray();
449                 for (int i = 0; i < arrayLen; i++) {
450                     jsonArray.put(serializeRange((Range)Array.get(keyValue,i)));
451                 }
452                 return new MetadataEntry(keyName, jsonArray);
453             } else if (elmtType instanceof ParameterizedType &&
454                     ((ParameterizedType)elmtType).getRawType() == Pair.class) {
455                 JSONArray jsonArray = new JSONArray();
456                 for (int i = 0; i < arrayLen; i++) {
457                     jsonArray.put(serializePair((Pair)Array.get(keyValue,i)));
458                 }
459                 return new MetadataEntry(keyName, jsonArray);
460             } else if (elmtType == MeteringRectangle.class) {
461                 JSONArray jsonArray = new JSONArray();
462                 for (int i = 0; i < arrayLen; i++) {
463                     jsonArray.put(serializeMeteringRectangle(
464                             (MeteringRectangle)Array.get(keyValue,i)));
465                 }
466                 return new MetadataEntry(keyName, jsonArray);
467             } else if (elmtType == Location.class) {
468                 JSONArray jsonArray = new JSONArray();
469                 for (int i = 0; i < arrayLen; i++) {
470                     jsonArray.put(serializeLocation((Location)Array.get(keyValue,i)));
471                 }
472                 return new MetadataEntry(keyName, jsonArray);
473             } else if (elmtType == RggbChannelVector.class) {
474                 JSONArray jsonArray = new JSONArray();
475                 for (int i = 0; i < arrayLen; i++) {
476                     jsonArray.put(serializeRggbChannelVector(
477                             (RggbChannelVector)Array.get(keyValue,i)));
478                 }
479                 return new MetadataEntry(keyName, jsonArray);
480             } else if (elmtType == BlackLevelPattern.class) {
481                 JSONArray jsonArray = new JSONArray();
482                 for (int i = 0; i < arrayLen; i++) {
483                     jsonArray.put(serializeBlackLevelPattern(
484                             (BlackLevelPattern)Array.get(keyValue,i)));
485                 }
486                 return new MetadataEntry(keyName, jsonArray);
487             } else if (elmtType == Point.class) {
488                 JSONArray jsonArray = new JSONArray();
489                 for (int i = 0; i < arrayLen; i++) {
490                     jsonArray.put(serializePoint((Point)Array.get(keyValue,i)));
491                 }
492                 return new MetadataEntry(keyName, jsonArray);
493             } else {
494                 Logt.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
495                 return null;
496             }
497         } catch (org.json.JSONException e) {
498             throw new ItsException("JSON error for key: " + keyName + ": ", e);
499         }
500     }
501 
serialize(RecordingResult recordingResult)502     public static JSONObject serialize(RecordingResult recordingResult)
503             throws ItsException {
504         JSONObject jsonObj = new JSONObject();
505         for (CaptureResult.Key<?> key : recordingResult.getKeys()) {
506             Object value = recordingResult.getResult(key);
507             if (value == null) {
508                 Logt.w(TAG, "Key value is null for: " + key.toString());
509                 continue;
510             }
511             Type keyType = value.getClass();
512             MetadataEntry entry;
513             if (keyType instanceof GenericArrayType) {
514                 entry = serializeArrayEntry(keyType, key, value);
515             } else {
516                 entry = serializeEntry(keyType, key, value);
517             }
518             try {
519                 if (entry != null) {
520                     jsonObj.put(entry.key, entry.value);
521                 } else {
522                     Logt.w(TAG, "Key entry is null for: " + key.toString());
523                 }
524             } catch (org.json.JSONException e) {
525                 throw new ItsException("JSON error for key: " + key.getName() + ": ", e);
526             }
527         }
528         return jsonObj;
529     }
530 
531     @SuppressWarnings("unchecked")
serialize(CameraMetadata md)532     public static JSONObject serialize(CameraMetadata md)
533             throws ItsException {
534         JSONObject jsonObj = new JSONObject();
535         Field[] allFields = md.getClass().getDeclaredFields();
536         if (md.getClass() == TotalCaptureResult.class) {
537             allFields = CaptureResult.class.getDeclaredFields();
538         }
539         if (md.getClass() == CameraCharacteristics.class) {
540             // Special handling for information not stored in metadata keys
541             CameraCharacteristics chars = (CameraCharacteristics) md;
542             List<CameraCharacteristics.Key<?>> charsKeys = chars.getKeys();
543             List<CaptureRequest.Key<?>> requestKeys = chars.getAvailableCaptureRequestKeys();
544             List<CaptureResult.Key<?>> resultKeys = chars.getAvailableCaptureResultKeys();
545             Set<String> physicalCamIds = chars.getPhysicalCameraIds();
546 
547             try {
548                 JSONArray charKeysArr = new JSONArray();
549                 for (CameraCharacteristics.Key<?> k : charsKeys) {
550                     charKeysArr.put(k.getName());
551                 }
552                 JSONArray reqKeysArr = new JSONArray();
553                 for (CaptureRequest.Key<?> k : requestKeys) {
554                     reqKeysArr.put(k.getName());
555                 }
556                 JSONArray resKeysArr = new JSONArray();
557                 for (CaptureResult.Key<?> k : resultKeys) {
558                     resKeysArr.put(k.getName());
559                 }
560                 // Avoid using the hidden metadata key name here to prevent confliction
561                 jsonObj.put("camera.characteristics.keys", charKeysArr);
562                 jsonObj.put("camera.characteristics.requestKeys", reqKeysArr);
563                 jsonObj.put("camera.characteristics.resultKeys", resKeysArr);
564 
565                 if (!physicalCamIds.isEmpty()) {
566                     JSONArray physCamIdsArr = new JSONArray();
567                     for (String id : physicalCamIds) {
568                         physCamIdsArr.put(id);
569                     }
570                     jsonObj.put("camera.characteristics.physicalCamIds", physCamIdsArr);
571                 }
572             } catch (org.json.JSONException e) {
573                 throw new ItsException("JSON error for CameraCharacteristics:", e);
574             }
575         }
576         for (Field field : allFields) {
577             if (Modifier.isPublic(field.getModifiers()) &&
578                     Modifier.isStatic(field.getModifiers()) &&
579                     (field.getType() == CaptureRequest.Key.class
580                       || field.getType() == CaptureResult.Key.class
581                       || field.getType() == TotalCaptureResult.Key.class
582                       || field.getType() == CameraCharacteristics.Key.class) &&
583                     field.getGenericType() instanceof ParameterizedType) {
584                 ParameterizedType paramType = (ParameterizedType)field.getGenericType();
585                 Type[] argTypes = paramType.getActualTypeArguments();
586                 if (argTypes.length > 0) {
587                     try {
588                         Type keyType = argTypes[0];
589                         Object keyObj = field.get(md);
590                         MetadataEntry entry;
591                         if (keyType instanceof GenericArrayType) {
592                             entry = serializeArrayEntry(keyType, keyObj, md);
593                         } else {
594                             entry = serializeEntry(keyType, keyObj, md);
595                         }
596 
597                         // TODO: Figure this weird case out.
598                         // There is a weird case where the entry is non-null but the toString
599                         // of the entry is null, and if this happens, the null-ness spreads like
600                         // a virus and makes the whole JSON object null from the top level down.
601                         // Not sure if it's a bug in the library or I'm just not using it right.
602                         // Workaround by checking for this case explicitly and not adding the
603                         // value to the jsonObj when it is detected.
604                         if (entry != null && entry.key != null && entry.value != null
605                                           && entry.value.toString() == null) {
606                             Logt.w(TAG, "Error encountered serializing value for key: " + entry.key);
607                         } else if (entry != null) {
608                             jsonObj.put(entry.key, entry.value);
609                         } else {
610                             // Ignore.
611                         }
612                     } catch (IllegalAccessException e) {
613                         throw new ItsException(
614                                 "Access error for field: " + field + ": ", e);
615                     } catch (org.json.JSONException e) {
616                         throw new ItsException(
617                                 "JSON error for field: " + field + ": ", e);
618                     }
619                 }
620             }
621         }
622         return jsonObj;
623     }
624 
625     @SuppressWarnings("unchecked")
deserialize(CaptureRequest.Builder mdDefault, JSONObject jsonReq)626     public static CaptureRequest.Builder deserialize(CaptureRequest.Builder mdDefault,
627             JSONObject jsonReq) throws ItsException {
628         try {
629             Logt.i(TAG, "Parsing JSON capture request ...");
630 
631             // Iterate over the CaptureRequest reflected fields.
632             CaptureRequest.Builder md = mdDefault;
633             Field[] allFields = CaptureRequest.class.getDeclaredFields();
634             for (Field field : allFields) {
635                 if (Modifier.isPublic(field.getModifiers()) &&
636                         Modifier.isStatic(field.getModifiers()) &&
637                         field.getType() == CaptureRequest.Key.class &&
638                         field.getGenericType() instanceof ParameterizedType) {
639                     ParameterizedType paramType = (ParameterizedType)field.getGenericType();
640                     Type[] argTypes = paramType.getActualTypeArguments();
641                     if (argTypes.length > 0) {
642                         CaptureRequest.Key key = (CaptureRequest.Key)field.get(md);
643                         String keyName = key.getName();
644                         Type keyType = argTypes[0];
645 
646                         // For each reflected CaptureRequest entry, look inside the JSON object
647                         // to see if it is being set. If it is found, remove the key from the
648                         // JSON object. After this process, there should be no keys left in the
649                         // JSON (otherwise an invalid key was specified).
650 
651                         if (jsonReq.has(keyName) && !jsonReq.isNull(keyName)) {
652                             if (keyType instanceof GenericArrayType) {
653                                 Type elmtType =
654                                         ((GenericArrayType)keyType).getGenericComponentType();
655                                 JSONArray ja = jsonReq.getJSONArray(keyName);
656                                 Object val[] = new Object[ja.length()];
657                                 for (int i = 0; i < ja.length(); i++) {
658                                     if (elmtType == int.class) {
659                                         Array.set(val, i, ja.getInt(i));
660                                     } else if (elmtType == byte.class) {
661                                         Array.set(val, i, (byte)ja.getInt(i));
662                                     } else if (elmtType == float.class) {
663                                         Array.set(val, i, (float)ja.getDouble(i));
664                                     } else if (elmtType == long.class) {
665                                         Array.set(val, i, ja.getLong(i));
666                                     } else if (elmtType == double.class) {
667                                         Array.set(val, i, ja.getDouble(i));
668                                     } else if (elmtType == boolean.class) {
669                                         Array.set(val, i, ja.getBoolean(i));
670                                     } else if (elmtType == String.class) {
671                                         Array.set(val, i, ja.getString(i));
672                                     } else if (elmtType == Size.class){
673                                         JSONObject obj = ja.getJSONObject(i);
674                                         Array.set(val, i, new Size(
675                                                 obj.getInt("width"), obj.getInt("height")));
676                                     } else if (elmtType == Rect.class) {
677                                         JSONObject obj = ja.getJSONObject(i);
678                                         Array.set(val, i, new Rect(
679                                                 obj.getInt("left"), obj.getInt("top"),
680                                                 obj.getInt("bottom"), obj.getInt("right")));
681                                     } else if (elmtType == Rational.class) {
682                                         JSONObject obj = ja.getJSONObject(i);
683                                         Array.set(val, i, new Rational(
684                                                 obj.getInt("numerator"),
685                                                 obj.getInt("denominator")));
686                                     } else if (elmtType == RggbChannelVector.class) {
687                                         JSONArray arr = ja.getJSONArray(i);
688                                         Array.set(val, i, new RggbChannelVector(
689                                                 (float)arr.getDouble(0),
690                                                 (float)arr.getDouble(1),
691                                                 (float)arr.getDouble(2),
692                                                 (float)arr.getDouble(3)));
693                                     } else if (elmtType == ColorSpaceTransform.class) {
694                                         JSONArray arr = ja.getJSONArray(i);
695                                         Rational xform[] = new Rational[9];
696                                         for (int j = 0; j < 9; j++) {
697                                             xform[j] = new Rational(
698                                                     arr.getJSONObject(j).getInt("numerator"),
699                                                     arr.getJSONObject(j).getInt("denominator"));
700                                         }
701                                         Array.set(val, i, new ColorSpaceTransform(xform));
702                                     } else if (elmtType == MeteringRectangle.class) {
703                                         JSONObject obj = ja.getJSONObject(i);
704                                         Array.set(val, i, new MeteringRectangle(
705                                                 obj.getInt("x"),
706                                                 obj.getInt("y"),
707                                                 obj.getInt("width"),
708                                                 obj.getInt("height"),
709                                                 obj.getInt("weight")));
710                                     } else {
711                                         throw new ItsException(
712                                                 "Failed to parse key from JSON: " + keyName);
713                                     }
714                                 }
715                                 if (val != null) {
716                                     Logt.i(TAG, "Set: "+keyName+" -> "+Arrays.toString(val));
717                                     md.set(key, val);
718                                     jsonReq.remove(keyName);
719                                 }
720                             } else {
721                                 Object val = null;
722                                 if (keyType == Integer.class) {
723                                     val = jsonReq.getInt(keyName);
724                                 } else if (keyType == Byte.class) {
725                                     val = (byte)jsonReq.getInt(keyName);
726                                 } else if (keyType == Double.class) {
727                                     val = jsonReq.getDouble(keyName);
728                                 } else if (keyType == Long.class) {
729                                     val = jsonReq.getLong(keyName);
730                                 } else if (keyType == Float.class) {
731                                     val = (float)jsonReq.getDouble(keyName);
732                                 } else if (keyType == Boolean.class) {
733                                     val = jsonReq.getBoolean(keyName);
734                                 } else if (keyType == String.class) {
735                                     val = jsonReq.getString(keyName);
736                                 } else if (keyType == Size.class) {
737                                     JSONObject obj = jsonReq.getJSONObject(keyName);
738                                     val = new Size(
739                                             obj.getInt("width"), obj.getInt("height"));
740                                 } else if (keyType == Rect.class) {
741                                     JSONObject obj = jsonReq.getJSONObject(keyName);
742                                     val = new Rect(
743                                             obj.getInt("left"), obj.getInt("top"),
744                                             obj.getInt("right"), obj.getInt("bottom"));
745                                 } else if (keyType == Rational.class) {
746                                     JSONObject obj = jsonReq.getJSONObject(keyName);
747                                     val = new Rational(obj.getInt("numerator"),
748                                                        obj.getInt("denominator"));
749                                 } else if (keyType == RggbChannelVector.class) {
750                                     JSONObject obj = jsonReq.optJSONObject(keyName);
751                                     JSONArray arr = jsonReq.optJSONArray(keyName);
752                                     if (arr != null) {
753                                         val = new RggbChannelVector(
754                                                 (float)arr.getDouble(0),
755                                                 (float)arr.getDouble(1),
756                                                 (float)arr.getDouble(2),
757                                                 (float)arr.getDouble(3));
758                                     } else if (obj != null) {
759                                         val = new RggbChannelVector(
760                                                 (float)obj.getDouble("red"),
761                                                 (float)obj.getDouble("greenEven"),
762                                                 (float)obj.getDouble("greenOdd"),
763                                                 (float)obj.getDouble("blue"));
764                                     } else {
765                                         throw new ItsException("Invalid RggbChannelVector object");
766                                     }
767                                 } else if (keyType == ColorSpaceTransform.class) {
768                                     JSONArray arr = jsonReq.getJSONArray(keyName);
769                                     Rational a[] = new Rational[9];
770                                     for (int i = 0; i < 9; i++) {
771                                         a[i] = new Rational(
772                                                 arr.getJSONObject(i).getInt("numerator"),
773                                                 arr.getJSONObject(i).getInt("denominator"));
774                                     }
775                                     val = new ColorSpaceTransform(a);
776                                 } else if (keyType == TonemapCurve.class) {
777                                     JSONObject obj = jsonReq.optJSONObject(keyName);
778                                     String names[] = {"red", "green", "blue"};
779                                     float[][] curves = new float[3][];
780                                     for (int ch = 0; ch < 3; ch++) {
781                                         JSONArray ja = obj.getJSONArray(names[ch]);
782                                         curves[ch] = new float[ja.length()];
783                                         for (int i = 0; i < ja.length(); i++) {
784                                             Array.set(curves[ch], i, (float)ja.getDouble(i));
785                                         }
786                                     }
787                                     val = new TonemapCurve(curves[0], curves[1], curves[2]);
788                                 } else if (keyType instanceof ParameterizedType &&
789                                         ((ParameterizedType)keyType).getRawType() == Range.class &&
790                                         ((ParameterizedType)keyType).getActualTypeArguments().length == 1 &&
791                                         ((ParameterizedType)keyType).getActualTypeArguments()[0] == Integer.class) {
792                                     JSONArray arr = jsonReq.getJSONArray(keyName);
793                                     val = new Range<Integer>(arr.getInt(0), arr.getInt(1));
794                                 } else {
795                                     throw new ItsException(
796                                             "Failed to parse key from JSON: " +
797                                             keyName + ", " + keyType);
798                                 }
799                                 if (val != null) {
800                                     Logt.i(TAG, "Set: " + keyName + " -> " + val);
801                                     md.set(key ,val);
802                                     jsonReq.remove(keyName);
803                                 }
804                             }
805                         }
806                     }
807                 }
808             }
809 
810             // Ensure that there were no invalid keys in the JSON request object.
811             if (jsonReq.length() != 0) {
812                 throw new ItsException("Invalid JSON key(s): " + jsonReq.toString());
813             }
814 
815             Logt.i(TAG, "Parsing JSON capture request completed");
816             return md;
817         } catch (java.lang.IllegalAccessException e) {
818             throw new ItsException("Access error: ", e);
819         } catch (org.json.JSONException e) {
820             throw new ItsException("JSON error: ", e);
821         }
822     }
823 
824     @SuppressWarnings("unchecked")
deserializeRequestList( CameraDevice device, JSONObject jsonObjTop, String requestKey)825     public static List<CaptureRequest.Builder> deserializeRequestList(
826             CameraDevice device, JSONObject jsonObjTop, String requestKey)
827             throws ItsException {
828         try {
829             List<CaptureRequest.Builder> requests = null;
830             JSONArray jsonReqs = jsonObjTop.getJSONArray(requestKey);
831             requests = new LinkedList<CaptureRequest.Builder>();
832             for (int i = 0; i < jsonReqs.length(); i++) {
833                 CaptureRequest.Builder templateReq = device.createCaptureRequest(
834                         CameraDevice.TEMPLATE_STILL_CAPTURE);
835                 requests.add(
836                     deserialize(templateReq, jsonReqs.getJSONObject(i)));
837             }
838             return requests;
839         } catch (org.json.JSONException e) {
840             throw new ItsException("JSON error: ", e);
841         } catch (android.hardware.camera2.CameraAccessException e) {
842             throw new ItsException("Access error: ", e);
843         }
844     }
845 }
846