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