1 /*
2  * Copyright (C) 2022 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.car.hal.fakevhal;
18 
19 import android.annotation.Nullable;
20 import android.car.builtin.util.Slogf;
21 import android.hardware.automotive.vehicle.AccessForVehicleProperty;
22 import android.hardware.automotive.vehicle.ChangeModeForVehicleProperty;
23 import android.hardware.automotive.vehicle.EvStoppingMode;
24 import android.hardware.automotive.vehicle.PortLocationType;
25 import android.hardware.automotive.vehicle.RawPropValues;
26 import android.hardware.automotive.vehicle.TestVendorProperty;
27 import android.hardware.automotive.vehicle.VehicleAreaConfig;
28 import android.hardware.automotive.vehicle.VehicleAreaDoor;
29 import android.hardware.automotive.vehicle.VehicleAreaMirror;
30 import android.hardware.automotive.vehicle.VehicleAreaSeat;
31 import android.hardware.automotive.vehicle.VehicleAreaWheel;
32 import android.hardware.automotive.vehicle.VehicleAreaWindow;
33 import android.hardware.automotive.vehicle.VehicleHvacFanDirection;
34 import android.hardware.automotive.vehicle.VehicleLightState;
35 import android.hardware.automotive.vehicle.VehicleLightSwitch;
36 import android.hardware.automotive.vehicle.VehiclePropConfig;
37 import android.hardware.automotive.vehicle.VehicleProperty;
38 import android.util.JsonReader;
39 import android.util.JsonToken;
40 import android.util.MalformedJsonException;
41 import android.util.Pair;
42 import android.util.SparseArray;
43 
44 import com.android.car.CarLog;
45 import com.android.car.internal.util.IntArray;
46 import com.android.car.internal.util.LongArray;
47 
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.InputStreamReader;
53 import java.lang.reflect.Field;
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Objects;
58 
59 /**
60  * A JSON parser class to get configs and values from JSON config files.
61  */
62 public final class FakeVhalConfigParser {
63 
64     private static final String TAG = CarLog.tagFor(FakeVhalConfigParser.class);
65 
66     // Defined VehiclePropertyAccess is always 0+
67     private static final int ACCESS_NOT_SET = -1;
68 
69     private static final String ENUM_CLASS_DIRECTORY = "android.hardware.automotive.vehicle.";
70     private static final String JSON_FIELD_NAME_ROOT = "properties";
71     private static final String JSON_FIELD_NAME_PROPERTY_ID = "property";
72     private static final String JSON_FIELD_NAME_DEFAULT_VALUE = "defaultValue";
73     private static final String JSON_FIELD_NAME_AREAS = "areas";
74     private static final String JSON_FIELD_NAME_CONFIG_ARRAY = "configArray";
75     private static final String JSON_FIELD_NAME_CONFIG_STRING = "configString";
76     private static final String JSON_FIELD_NAME_MIN_SAMPLE_RATE = "minSampleRate";
77     private static final String JSON_FIELD_NAME_MAX_SAMPLE_RATE = "maxSampleRate";
78     private static final String JSON_FIELD_NAME_AREA_ID = "areaId";
79     private static final String JSON_FIELD_NAME_INT32_VALUES = "int32Values";
80     private static final String JSON_FIELD_NAME_INT64_VALUES = "int64Values";
81     private static final String JSON_FIELD_NAME_FLOAT_VALUES = "floatValues";
82     private static final String JSON_FIELD_NAME_STRING_VALUE = "stringValue";
83     private static final String JSON_FIELD_NAME_MIN_INT32_VALUE = "minInt32Value";
84     private static final String JSON_FIELD_NAME_MAX_INT32_VALUE = "maxInt32Value";
85     private static final String JSON_FIELD_NAME_MIN_FLOAT_VALUE = "minFloatValue";
86     private static final String JSON_FIELD_NAME_MAX_FLOAT_VALUE = "maxFloatValue";
87     private static final String JSON_FIELD_NAME_ACCESS = "access";
88     private static final String JSON_FIELD_NAME_CHANGE_MODE = "changeMode";
89     private static final String JSON_FIELD_NAME_COMMENT = "comment";
90     // Following values are defined in PropertyUtils.h file
91     // (hardware/interfaces/automotive/vehicle/aidl/impl/utils/common/include/PropertyUtils.h).
92     private static final int DOOR_1_RIGHT = VehicleAreaDoor.ROW_1_RIGHT;
93     private static final int DOOR_1_LEFT = VehicleAreaDoor.ROW_1_LEFT;
94     private static final int DOOR_2_RIGHT = VehicleAreaDoor.ROW_2_RIGHT;
95     private static final int DOOR_2_LEFT = VehicleAreaDoor.ROW_2_LEFT;
96     private static final int DOOR_REAR = VehicleAreaDoor.REAR;
97     private static final int WINDOW_1_LEFT = VehicleAreaWindow.ROW_1_LEFT;
98     private static final int WINDOW_1_RIGHT = VehicleAreaWindow.ROW_1_RIGHT;
99     private static final int WINDOW_2_LEFT = VehicleAreaWindow.ROW_2_LEFT;
100     private static final int WINDOW_2_RIGHT = VehicleAreaWindow.ROW_2_RIGHT;
101     private static final int WINDOW_ROOF_TOP_1 = VehicleAreaWindow.ROOF_TOP_1;
102     private static final int SEAT_1_RIGHT = VehicleAreaSeat.ROW_1_RIGHT;
103     private static final int SEAT_1_LEFT = VehicleAreaSeat.ROW_1_LEFT;
104     private static final int SEAT_2_RIGHT = VehicleAreaSeat.ROW_2_RIGHT;
105     private static final int SEAT_2_LEFT = VehicleAreaSeat.ROW_2_LEFT;
106     private static final int SEAT_2_CENTER = VehicleAreaSeat.ROW_2_CENTER;
107     private static final int WHEEL_REAR_RIGHT = VehicleAreaWheel.RIGHT_REAR;
108     private static final int WHEEL_REAR_LEFT = VehicleAreaWheel.LEFT_REAR;
109     private static final int WHEEL_FRONT_RIGHT = VehicleAreaWheel.RIGHT_FRONT;
110     private static final int WHEEL_FRONT_LEFT = VehicleAreaWheel.LEFT_FRONT;
111     private static final int CHARGE_PORT_FRONT_LEFT = PortLocationType.FRONT_LEFT;
112     private static final int CHARGE_PORT_REAR_LEFT = PortLocationType.REAR_LEFT;
113     private static final int FAN_DIRECTION_UNKNOWN = VehicleHvacFanDirection.UNKNOWN;
114     private static final int FAN_DIRECTION_FLOOR = VehicleHvacFanDirection.FLOOR;
115     private static final int FAN_DIRECTION_FACE = VehicleHvacFanDirection.FACE;
116     private static final int FAN_DIRECTION_DEFROST = VehicleHvacFanDirection.DEFROST;
117     private static final int FUEL_DOOR_REAR_LEFT = PortLocationType.REAR_LEFT;
118     // TODO(b/241984846) Keep SEAT_2_CENTER from HVAC_LEFT here. May have a new design to handle
119     //  HVAC zone ids.
120     private static final int HVAC_FRONT_ROW = SEAT_1_LEFT | SEAT_1_RIGHT;
121     private static final int HVAC_REAR_ROW = SEAT_2_LEFT | SEAT_2_CENTER | SEAT_2_RIGHT;
122     private static final int HVAC_LEFT = SEAT_1_LEFT | SEAT_2_LEFT | SEAT_2_CENTER;
123     private static final int HVAC_RIGHT = SEAT_1_RIGHT | SEAT_2_RIGHT;
124     private static final int HVAC_ALL = HVAC_LEFT | HVAC_RIGHT;
125     private static final int LIGHT_STATE_ON = VehicleLightState.ON;
126     private static final int LIGHT_STATE_OFF = VehicleLightState.OFF;
127     private static final int LIGHT_SWITCH_ON = VehicleLightSwitch.ON;
128     private static final int LIGHT_SWITCH_OFF = VehicleLightSwitch.OFF;
129     private static final int LIGHT_SWITCH_AUTO = VehicleLightSwitch.AUTOMATIC;
130     private static final int EV_STOPPING_MODE_CREEP = EvStoppingMode.CREEP;
131     private static final int EV_STOPPING_MODE_ROLL = EvStoppingMode.ROLL;
132     private static final int EV_STOPPING_MODE_HOLD = EvStoppingMode.HOLD;
133     private static final int MIRROR_DRIVER_LEFT_RIGHT = VehicleAreaMirror.DRIVER_LEFT
134                                 | VehicleAreaMirror.DRIVER_RIGHT;
135 
136     private static final Map<String, Integer> CONSTANTS_BY_NAME = Map.ofEntries(
137             Map.entry("DOOR_1_RIGHT", DOOR_1_RIGHT),
138             Map.entry("DOOR_1_LEFT", DOOR_1_LEFT),
139             Map.entry("DOOR_2_RIGHT", DOOR_2_RIGHT),
140             Map.entry("DOOR_2_LEFT", DOOR_2_LEFT),
141             Map.entry("DOOR_REAR", DOOR_REAR),
142             Map.entry("HVAC_ALL", HVAC_ALL),
143             Map.entry("HVAC_LEFT", HVAC_LEFT),
144             Map.entry("HVAC_RIGHT", HVAC_RIGHT),
145             Map.entry("HVAC_FRONT_ROW", HVAC_FRONT_ROW),
146             Map.entry("HVAC_REAR_ROW", HVAC_REAR_ROW),
147             Map.entry("VENDOR_EXTENSION_INT_PROPERTY",
148                     TestVendorProperty.VENDOR_EXTENSION_INT_PROPERTY),
149             Map.entry("VENDOR_EXTENSION_BOOLEAN_PROPERTY",
150                     TestVendorProperty.VENDOR_EXTENSION_BOOLEAN_PROPERTY),
151             Map.entry("VENDOR_EXTENSION_STRING_PROPERTY",
152                     TestVendorProperty.VENDOR_EXTENSION_STRING_PROPERTY),
153             Map.entry("VENDOR_EXTENSION_FLOAT_PROPERTY",
154                     TestVendorProperty.VENDOR_EXTENSION_FLOAT_PROPERTY),
155             Map.entry("WINDOW_1_LEFT", WINDOW_1_LEFT),
156             Map.entry("WINDOW_1_RIGHT", WINDOW_1_RIGHT),
157             Map.entry("WINDOW_2_LEFT", WINDOW_2_LEFT),
158             Map.entry("WINDOW_2_RIGHT", WINDOW_2_RIGHT),
159             Map.entry("WINDOW_ROOF_TOP_1", WINDOW_ROOF_TOP_1),
160             Map.entry("WINDOW_1_RIGHT_2_LEFT_2_RIGHT", WINDOW_1_RIGHT | WINDOW_2_LEFT
161                     | WINDOW_2_RIGHT),
162             Map.entry("SEAT_1_LEFT", SEAT_1_LEFT),
163             Map.entry("SEAT_1_RIGHT", SEAT_1_RIGHT),
164             Map.entry("SEAT_2_LEFT", SEAT_2_LEFT),
165             Map.entry("SEAT_2_RIGHT", SEAT_2_RIGHT),
166             Map.entry("SEAT_2_CENTER", SEAT_2_CENTER),
167             Map.entry("SEAT_2_LEFT_2_RIGHT_2_CENTER", SEAT_2_LEFT | SEAT_2_RIGHT | SEAT_2_CENTER),
168             Map.entry("WHEEL_REAR_RIGHT", WHEEL_REAR_RIGHT),
169             Map.entry("WHEEL_REAR_LEFT", WHEEL_REAR_LEFT),
170             Map.entry("WHEEL_FRONT_RIGHT", WHEEL_FRONT_RIGHT),
171             Map.entry("WHEEL_FRONT_LEFT", WHEEL_FRONT_LEFT),
172             Map.entry("CHARGE_PORT_FRONT_LEFT", CHARGE_PORT_FRONT_LEFT),
173             Map.entry("CHARGE_PORT_REAR_LEFT", CHARGE_PORT_REAR_LEFT),
174             Map.entry("FAN_DIRECTION_UNKNOWN", FAN_DIRECTION_UNKNOWN),
175             Map.entry("FAN_DIRECTION_FLOOR", FAN_DIRECTION_FLOOR),
176             Map.entry("FAN_DIRECTION_FACE", FAN_DIRECTION_FACE),
177             Map.entry("FAN_DIRECTION_DEFROST", FAN_DIRECTION_DEFROST),
178             Map.entry("FAN_DIRECTION_FACE_FLOOR", FAN_DIRECTION_FACE | FAN_DIRECTION_FLOOR),
179             Map.entry("FAN_DIRECTION_FACE_DEFROST", FAN_DIRECTION_FACE | FAN_DIRECTION_DEFROST),
180             Map.entry("FAN_DIRECTION_FLOOR_DEFROST", FAN_DIRECTION_FLOOR | FAN_DIRECTION_DEFROST),
181             Map.entry("FAN_DIRECTION_FLOOR_DEFROST_FACE", FAN_DIRECTION_FLOOR
182                     | FAN_DIRECTION_DEFROST | FAN_DIRECTION_FACE),
183             Map.entry("FUEL_DOOR_REAR_LEFT", FUEL_DOOR_REAR_LEFT),
184             Map.entry("LIGHT_STATE_ON", LIGHT_STATE_ON),
185             Map.entry("LIGHT_STATE_OFF", LIGHT_STATE_OFF),
186             Map.entry("LIGHT_SWITCH_ON", LIGHT_SWITCH_ON),
187             Map.entry("LIGHT_SWITCH_OFF", LIGHT_SWITCH_OFF),
188             Map.entry("LIGHT_SWITCH_AUTO", LIGHT_SWITCH_AUTO),
189             Map.entry("EV_STOPPING_MODE_CREEP", EV_STOPPING_MODE_CREEP),
190             Map.entry("EV_STOPPING_MODE_ROLL", EV_STOPPING_MODE_ROLL),
191             Map.entry("EV_STOPPING_MODE_HOLD", EV_STOPPING_MODE_HOLD),
192             Map.entry("MIRROR_DRIVER_LEFT_RIGHT", MIRROR_DRIVER_LEFT_RIGHT),
193             Map.entry("ECHO_REVERSE_BYTES", TestVendorProperty.ECHO_REVERSE_BYTES),
194             Map.entry("VENDOR_PROPERTY_FOR_ERROR_CODE_TESTING",
195                     TestVendorProperty.VENDOR_PROPERTY_FOR_ERROR_CODE_TESTING),
196             Map.entry("kMixedTypePropertyForTest", TestVendorProperty.MIXED_TYPE_PROPERTY_FOR_TEST),
197             Map.entry("VENDOR_CLUSTER_NAVIGATION_STATE",
198                     TestVendorProperty.VENDOR_CLUSTER_NAVIGATION_STATE),
199             Map.entry("VENDOR_CLUSTER_REQUEST_DISPLAY",
200                     TestVendorProperty.VENDOR_CLUSTER_REQUEST_DISPLAY),
201             Map.entry("VENDOR_CLUSTER_SWITCH_UI", TestVendorProperty.VENDOR_CLUSTER_SWITCH_UI),
202             Map.entry("VENDOR_CLUSTER_DISPLAY_STATE",
203                     TestVendorProperty.VENDOR_CLUSTER_DISPLAY_STATE),
204             Map.entry("VENDOR_CLUSTER_REPORT_STATE",
205                     TestVendorProperty.VENDOR_CLUSTER_REPORT_STATE),
206             Map.entry("PLACEHOLDER_PROPERTY_INT", TestVendorProperty.PLACEHOLDER_PROPERTY_INT),
207             Map.entry("PLACEHOLDER_PROPERTY_FLOAT", TestVendorProperty.PLACEHOLDER_PROPERTY_FLOAT),
208             Map.entry("PLACEHOLDER_PROPERTY_BOOLEAN",
209                     TestVendorProperty.PLACEHOLDER_PROPERTY_BOOLEAN),
210             Map.entry("PLACEHOLDER_PROPERTY_STRING",
211                     TestVendorProperty.PLACEHOLDER_PROPERTY_STRING)
212     );
213 
214     /**
215      * Reads custom config files and parses the JSON root object whose field name is "properties".
216      *
217      * @param customConfigFile The custom config JSON file to parse from.
218      * @return a list of {@link ConfigDeclaration} storing configs and values for each property.
219      * @throws IOException if unable to read the config file.
220      * @throws IllegalArgumentException if the config file content is not in expected format.
221      */
parseJsonConfig(File customConfigFile)222     public SparseArray<ConfigDeclaration> parseJsonConfig(File customConfigFile) throws
223             IOException, IllegalArgumentException {
224         // Check if config file exists.
225         if (!isFileValid(customConfigFile)) {
226             Slogf.w(TAG, "Custom config file: %s is not a valid file.", customConfigFile.getPath());
227             return new SparseArray<>(/* initialCapacity= */ 0);
228         }
229 
230         FileInputStream customConfigFileStream = new FileInputStream(customConfigFile);
231         if (customConfigFileStream.available() == 0) {
232             Slogf.w(TAG, "Custom config file: %s is empty.", customConfigFile.getPath());
233             return new SparseArray<>(/* initialCapacity= */ 0);
234         }
235         return parseJsonConfig(customConfigFileStream);
236     }
237 
238     /**
239      * Reads default config file from java resources which is in the type of input stream.
240      *
241      * @param configInputStream The {@link InputStream} to parse from.
242      * @return a list of {@link ConfigDeclaration} storing configs and values for each property.
243      * @throws IOException if unable to read the config file.
244      * @throws IllegalArgumentException if file is not a valid expected JSON file.
245      */
parseJsonConfig(InputStream configInputStream)246     public SparseArray<ConfigDeclaration> parseJsonConfig(InputStream configInputStream)
247             throws IOException {
248         SparseArray<ConfigDeclaration> allPropConfigs = new SparseArray<>();
249         List<String> errors = new ArrayList<>();
250         // A wrapper to be effective final. We need the wrapper to be passed into lambda. Only one
251         // element is expected.
252         List<Boolean> rootFound = new ArrayList<>();
253 
254         try (var reader = new JsonReader(new InputStreamReader(configInputStream, "UTF-8"))) {
255             reader.setLenient(true);
256             int parsed = parseObjectEntry(reader, (String fieldName) -> {
257                 if (!fieldName.equals(JSON_FIELD_NAME_ROOT)) {
258                     reader.skipValue();
259                     return;
260                 }
261 
262                 rootFound.add(true);
263                 int propertyParsed = parseArrayEntry(reader, (int index) -> {
264                     ConfigDeclaration propConfig = parseEachProperty(reader, index, errors);
265                     if (propConfig == null) {
266                         errors.add("Unable to parse property config at index " + index);
267                         if (allPropConfigs.size() != 0) {
268                             errors.add("Last successfully parsed property Id: "
269                                     + allPropConfigs.valueAt(allPropConfigs.size() - 1)
270                                             .getConfig().prop);
271                         }
272                         return;
273                     }
274                     allPropConfigs.put(propConfig.getConfig().prop, propConfig);
275                 });
276 
277                 if (propertyParsed < 0) {
278                     throw new IllegalArgumentException(JSON_FIELD_NAME_ROOT
279                             + " field value is not a valid JSONArray.");
280                 }
281             });
282             if (parsed < 0) {
283                 throw new IllegalArgumentException(
284                         "This file does not contain a valid JSONObject.");
285             }
286         } catch (IllegalStateException | MalformedJsonException e) {
287             // Captures all unexpected json parsing error.
288             throw new IllegalArgumentException("Invalid json syntax", e);
289         }
290 
291         if (rootFound.size() == 0) {
292             throw new IllegalArgumentException("Missing root field: " + JSON_FIELD_NAME_ROOT);
293         }
294         if (!errors.isEmpty()) {
295             throw new IllegalArgumentException(String.join("\n", errors));
296         }
297         return allPropConfigs;
298     }
299 
300     /**
301      * Parses each property for its configs and values.
302      *
303      * @param reader The reader to parse.
304      * @param index The property index.
305      * @param errors A list to keep all errors.
306      * @return a {@link ConfigDeclaration} instance, null if failed to parse.
307      */
308     @Nullable
parseEachProperty(JsonReader reader, int index, List<String> errors)309     private ConfigDeclaration parseEachProperty(JsonReader reader, int index, List<String> errors)
310             throws IOException {
311         int initialErrorCount = errors.size();
312 
313         VehiclePropConfig vehiclePropConfig = new VehiclePropConfig();
314         vehiclePropConfig.prop = VehicleProperty.INVALID;
315 
316         class Wrapper {
317             boolean mIsAccessSet;
318             boolean mIsChangeModeSet;
319             RawPropValues mRawPropValues;
320         }
321 
322         var wrapper = new Wrapper();
323         SparseArray<RawPropValues> defaultValuesByAreaId = new SparseArray<>();
324 
325         try {
326             int parsed = parseObjectEntry(reader, (String fieldName) -> {
327                 switch (fieldName) {
328                     case JSON_FIELD_NAME_PROPERTY_ID:
329                         vehiclePropConfig.prop = parseIntValue(reader, fieldName, errors);
330                         break;
331                     case JSON_FIELD_NAME_CONFIG_STRING:
332                         vehiclePropConfig.configString = parseStringValue(reader, fieldName,
333                             errors);
334                         break;
335                     case JSON_FIELD_NAME_MIN_SAMPLE_RATE:
336                         vehiclePropConfig.minSampleRate = parseFloatValue(reader, fieldName,
337                                 errors);
338                         break;
339                     case JSON_FIELD_NAME_MAX_SAMPLE_RATE:
340                         vehiclePropConfig.maxSampleRate = parseFloatValue(reader, fieldName,
341                                 errors);
342                         break;
343                     case JSON_FIELD_NAME_ACCESS:
344                         vehiclePropConfig.access = parseIntValue(reader, fieldName, errors);
345                         wrapper.mIsAccessSet = true;
346                         break;
347                     case JSON_FIELD_NAME_CHANGE_MODE:
348                         vehiclePropConfig.changeMode = parseIntValue(reader, fieldName, errors);
349                         wrapper.mIsChangeModeSet = true;
350                         break;
351                     case JSON_FIELD_NAME_CONFIG_ARRAY:
352                         vehiclePropConfig.configArray = parseIntArrayValue(reader, fieldName,
353                                 errors);
354                         break;
355                     case JSON_FIELD_NAME_DEFAULT_VALUE:
356                         wrapper.mRawPropValues = parseDefaultValue(reader, errors);
357                         break;
358                     case JSON_FIELD_NAME_AREAS:
359                         vehiclePropConfig.areaConfigs = parseAreaConfigs(reader,
360                                 defaultValuesByAreaId, errors);
361                         break;
362                     case JSON_FIELD_NAME_COMMENT:
363                         // The "comment" field is used for comment in the config files and is
364                         // ignored by the parser.
365                         reader.skipValue();
366                         break;
367                     default:
368                         Slogf.w(TAG, "%s is an unknown field name. It didn't get parsed.",
369                                 fieldName);
370                         reader.skipValue();
371                 }
372             });
373             if (parsed < 0) {
374                 errors.add(JSON_FIELD_NAME_ROOT
375                         + " array has an invalid JSON element at index " + index);
376                 return null;
377             }
378             if (parsed == 0) {
379                 errors.add("The property object at index" + index + " is empty.");
380                 return null;
381             }
382         } catch (IllegalArgumentException e) {
383             errors.add("Faild to parse property field, error: " + e.getMessage());
384             return null;
385         }
386 
387         if (vehiclePropConfig.prop == VehicleProperty.INVALID) {
388             errors.add("PropId is required for any property.");
389             return null;
390         }
391 
392         int propertyId = vehiclePropConfig.prop;
393 
394         if (!wrapper.mIsAccessSet) {
395             if (AccessForVehicleProperty.values.containsKey(propertyId)) {
396                 vehiclePropConfig.access = AccessForVehicleProperty.values
397                         .get(propertyId);
398             } else {
399                 errors.add("Access field is not set for this property: " + propertyId);
400             }
401         }
402 
403         if (!wrapper.mIsChangeModeSet) {
404             if (ChangeModeForVehicleProperty.values.containsKey(vehiclePropConfig.prop)) {
405                 vehiclePropConfig.changeMode = ChangeModeForVehicleProperty.values
406                         .get(propertyId);
407             } else {
408                 errors.add("ChangeMode field is not set for this property: " + propertyId);
409             }
410         }
411 
412         // If area access is not set, set it to the global access.
413         if (vehiclePropConfig.areaConfigs != null) {
414             for (int i = 0; i < vehiclePropConfig.areaConfigs.length; i++) {
415                 if (vehiclePropConfig.areaConfigs[i].access == ACCESS_NOT_SET) {
416                     vehiclePropConfig.areaConfigs[i].access = vehiclePropConfig.access;
417                 }
418             }
419         }
420 
421         if (vehiclePropConfig.areaConfigs == null || vehiclePropConfig.areaConfigs.length == 0) {
422             VehicleAreaConfig areaConfig = new VehicleAreaConfig();
423             areaConfig.areaId = 0;
424             areaConfig.access = vehiclePropConfig.access;
425             areaConfig.supportVariableUpdateRate = true;
426             vehiclePropConfig.areaConfigs = new VehicleAreaConfig[]{areaConfig};
427         }
428 
429         if (errors.size() > initialErrorCount) {
430             return null;
431         }
432 
433         return new ConfigDeclaration(vehiclePropConfig, wrapper.mRawPropValues,
434                 defaultValuesByAreaId);
435     }
436 
437     /**
438      * Parses area configs array.
439      *
440      * @param reader The reader to parse.
441      * @param defaultValuesByAreaId The output default property value by specific area ID.
442      * @param errors The list to store all errors.
443      * @return a pair of configs and values for one area, null if failed to parse.
444      */
445     @Nullable
parseAreaConfigs(JsonReader reader, SparseArray<RawPropValues> defaultValuesByAreaId, List<String> errors)446     private VehicleAreaConfig[] parseAreaConfigs(JsonReader reader,
447             SparseArray<RawPropValues> defaultValuesByAreaId, List<String> errors)
448             throws IOException {
449         int initialErrorCount = errors.size();
450         List<VehicleAreaConfig> areaConfigs = new ArrayList<>();
451         int parsed = 0;
452 
453         try {
454             parsed = parseArrayEntry(reader, (int index) -> {
455                 Pair<VehicleAreaConfig, RawPropValues> result = parseAreaConfig(reader, index,
456                         errors);
457                 if (result != null) {
458                     areaConfigs.add(result.first);
459                     if (result.second != null) {
460                         defaultValuesByAreaId.put(result.first.areaId, result.second);
461                     }
462                 }
463             });
464         } catch (IllegalArgumentException e) {
465             errors.add("Failed to parse " + JSON_FIELD_NAME_AREAS + ", error: " + e.getMessage());
466             return null;
467         }
468 
469         if (parsed < 0) {
470             errors.add(JSON_FIELD_NAME_AREAS + " doesn't have a valid JSONArray value.");
471             return null;
472         }
473         if (errors.size() > initialErrorCount) {
474             return null;
475         }
476         return areaConfigs.toArray(new VehicleAreaConfig[areaConfigs.size()]);
477     }
478 
479     /**
480      * Parses area JSON config object.
481      *
482      * @param reader The reader to parse.
483      * @param errors The list to store all errors.
484      * @return a pair of configs and values for one area, null if failed to parse.
485      */
486     @Nullable
parseAreaConfig(JsonReader reader, int index, List<String> errors)487     private Pair<VehicleAreaConfig, RawPropValues> parseAreaConfig(JsonReader reader,
488             int index, List<String> errors) throws IOException {
489         int initialErrorCount = errors.size();
490         VehicleAreaConfig areaConfig = new VehicleAreaConfig();
491 
492         class Wrapper {
493             RawPropValues mDefaultValue;
494             boolean mHasAreaId;
495             boolean mIsAccessSet;
496         }
497         var wrapper = new Wrapper();
498         int parsed = 0;
499 
500         try {
501             parsed = parseObjectEntry(reader, (String fieldName) -> {
502                 switch (fieldName) {
503                     case JSON_FIELD_NAME_ACCESS:
504                         areaConfig.access = parseIntValue(reader, fieldName, errors);
505                         wrapper.mIsAccessSet = true;
506                         break;
507                     case JSON_FIELD_NAME_AREA_ID:
508                         areaConfig.areaId = parseIntValue(reader, fieldName, errors);
509                         wrapper.mHasAreaId = true;
510                         break;
511                     case JSON_FIELD_NAME_MIN_INT32_VALUE:
512                         areaConfig.minInt32Value = parseIntValue(reader, fieldName, errors);
513                         break;
514                     case JSON_FIELD_NAME_MAX_INT32_VALUE:
515                         areaConfig.maxInt32Value = parseIntValue(reader, fieldName, errors);
516                         break;
517                     case JSON_FIELD_NAME_MIN_FLOAT_VALUE:
518                         areaConfig.minFloatValue = parseFloatValue(reader, fieldName, errors);
519                         break;
520                     case JSON_FIELD_NAME_MAX_FLOAT_VALUE:
521                         areaConfig.maxFloatValue = parseFloatValue(reader, fieldName, errors);
522                         break;
523                     case JSON_FIELD_NAME_DEFAULT_VALUE:
524                         wrapper.mDefaultValue = parseDefaultValue(reader, errors);
525                         break;
526                     default:
527                         Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.",
528                                 fieldName);
529                         reader.skipValue();
530                 }
531             });
532         } catch (IllegalArgumentException e) {
533             errors.add("Failed to parse areaConfig, error: " + e.getMessage());
534             return null;
535         }
536 
537         if (parsed < 0) {
538             errors.add("Unable to get a JSONObject element for " + JSON_FIELD_NAME_AREAS
539                     + " at index " + index);
540             return null;
541         }
542         if (parsed == 0) {
543             errors.add("The JSONObject element for " + JSON_FIELD_NAME_AREAS + " at index "
544                     + index + " is empty.");
545             return null;
546         }
547 
548         if (!wrapper.mHasAreaId) {
549             errors.add(areaConfig + " doesn't have areaId. AreaId is required.");
550             return null;
551         }
552 
553         if (errors.size() > initialErrorCount) {
554             return null;
555         }
556 
557         if (!wrapper.mIsAccessSet) {
558             areaConfig.access = ACCESS_NOT_SET;
559         }
560 
561         return Pair.create(areaConfig, wrapper.mDefaultValue);
562     }
563 
564     /**
565      * Parses the "defaultValue" field of a property object and area property object.
566      *
567      * @param reader The reader to parse.
568      * @param errors The list to store all errors.
569      * @return a {@link RawPropValues} object which stores defaultValue, null if failed to parse.
570      */
571     @Nullable
parseDefaultValue(JsonReader reader, List<String> errors)572     private RawPropValues parseDefaultValue(JsonReader reader, List<String> errors)
573             throws IOException {
574         int initialErrorCount = errors.size();
575         RawPropValues rawPropValues = new RawPropValues();
576         int parsed = 0;
577 
578         try {
579             parsed = parseObjectEntry(reader, (String fieldName) -> {
580                 switch (fieldName) {
581                     case JSON_FIELD_NAME_INT32_VALUES: {
582                         rawPropValues.int32Values = parseIntArrayValue(reader, fieldName,  errors);
583                         break;
584                     }
585                     case JSON_FIELD_NAME_INT64_VALUES: {
586                         rawPropValues.int64Values = parseLongArrayValue(reader, fieldName, errors);
587                         break;
588                     }
589                     case JSON_FIELD_NAME_FLOAT_VALUES: {
590                         rawPropValues.floatValues = parseFloatArrayValue(reader, fieldName, errors);
591                         break;
592                     }
593                     case JSON_FIELD_NAME_STRING_VALUE: {
594                         rawPropValues.stringValue = parseStringValue(reader, fieldName, errors);
595                         break;
596                     }
597                     default:
598                         Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.",
599                                 fieldName);
600                         reader.skipValue();
601                 }
602             });
603         } catch (IllegalArgumentException e) {
604             errors.add("Failed to parse " + JSON_FIELD_NAME_DEFAULT_VALUE + ", error: "
605                      + e.getMessage());
606             return null;
607         }
608 
609         if (parsed < 0) {
610             errors.add(JSON_FIELD_NAME_DEFAULT_VALUE + " doesn't have a valid JSONObject value");
611             return null;
612         }
613         if (parsed == 0) {
614             Slogf.w(TAG, JSON_FIELD_NAME_DEFAULT_VALUE + " is empty, assuming no overwrite");
615             return null;
616         }
617         if (errors.size() > initialErrorCount) {
618             return null;
619         }
620         return rawPropValues;
621     }
622 
623     /**
624      * Parses String Json value.
625      *
626      * @param reader The reader to parse.
627      * @param fieldName Field name of JSON object name/value mapping.
628      * @param errors The list to store all errors.
629      * @return a string of parsed value, null if failed to parse.
630      */
631     @Nullable
parseStringValue(JsonReader reader, String fieldName, List<String> errors)632     private String parseStringValue(JsonReader reader, String fieldName,
633             List<String> errors) throws IOException {
634         try {
635             String result = nextStringAdvance(reader);
636             if (result.equals("")) {
637                 errors.add(fieldName + " doesn't have a valid string value.");
638                 return null;
639             }
640             return result;
641         } catch (Exception e) {
642             errors.add(fieldName + " doesn't have a valid string value.");
643             return null;
644         }
645     }
646 
647     /**
648      * Parses int Json value.
649      *
650      * @param reader The reader to parse.
651      * @param fieldName Field name of JSON object name/value mapping.
652      * @param errors The list to store all errors.
653      * @return a value as int, 0 if failed to parse.
654      */
parseIntValue(JsonReader reader, String fieldName, List<String> errors)655     private int parseIntValue(JsonReader reader,  String fieldName, List<String> errors)
656             throws IOException {
657         if (isString(reader)) {
658             String constantValue = nextStringAdvance(reader);
659             return parseConstantValue(constantValue, errors);
660         }
661         try {
662             return nextIntAdvance(reader);
663         } catch (Exception e) {
664             errors.add(fieldName + " doesn't have a valid int value.");
665             return 0;
666         }
667     }
668 
669     /**
670      * Parses float Json value.
671      *
672      * @param reader The reader to parse.
673      * @param fieldName Field name of JSON object name/value mapping.
674      * @param errors The list to store all errors.
675      * @return the parsed value as float, {@code 0f} if failed to parse.
676      */
parseFloatValue(JsonReader reader, String fieldName, List<String> errors)677     private float parseFloatValue(JsonReader reader, String fieldName, List<String> errors)
678             throws IOException {
679         if (isString(reader)) {
680             String constantValue = nextStringAdvance(reader);
681             return (float) parseConstantValue(constantValue, errors);
682         }
683         try {
684             return (float) nextDoubleAdvance(reader);
685         } catch (Exception e) {
686             errors.add(fieldName + " doesn't have a valid float value. " + e.getMessage());
687             return 0f;
688         }
689     }
690 
691     /**
692      * Parses enum class constant.
693      *
694      * @param stringValue The constant string to be parsed.
695      * @param errors A list to keep all errors.
696      * @return the int value of an enum constant, 0 if the constant format is invalid.
697      */
parseConstantValue(String stringValue, List<String> errors)698     private int parseConstantValue(String stringValue, List<String> errors) {
699         String[] propIdStrings = stringValue.split("::");
700         if (propIdStrings.length != 2 || propIdStrings[0].isEmpty() || propIdStrings[1].isEmpty()) {
701             errors.add(stringValue + " must in the form of <EnumClassName>::<ConstantName>.");
702             return 0;
703         }
704         String enumClassName = ENUM_CLASS_DIRECTORY + propIdStrings[0];
705         String constantName = propIdStrings[1];
706 
707         if (Objects.equals(propIdStrings[0], "Constants")) {
708             if (CONSTANTS_BY_NAME.containsKey(constantName)) {
709                 return CONSTANTS_BY_NAME.get(constantName);
710             }
711             errors.add(constantName + " is not a valid constant name.");
712             return 0;
713         }
714 
715         Class enumClass;
716         try {
717             enumClass = Class.forName(enumClassName);
718         } catch (ClassNotFoundException e) {
719             errors.add(enumClassName + " is not a valid class name. " + e.getMessage());
720             return 0;
721         }
722         Field[] fields = enumClass.getDeclaredFields();
723         for (Field field : fields) {
724             if (constantName.equals(field.getName())) {
725                 try {
726                     return field.getInt(enumClass);
727                 } catch (Exception e) {
728                     errors.add("Failed to get int value of " + enumClass + "." + constantName
729                             + " " + e.getMessage());
730                     return 0;
731                 }
732             }
733         }
734         errors.add(enumClass + " doesn't have a constant field with name " + constantName);
735         return 0;
736     }
737 
738     /**
739      * Parses a intger JSON array.
740      *
741      * @param reader The reader to parse.
742      * @param fieldName Field name of JSON object name/value mapping.
743      * @param errors The list to store all errors.
744      * @return an int array of default values, null if failed to parse.
745      */
746     @Nullable
parseIntArrayValue(JsonReader reader, String fieldName, List<String> errors)747     private int[] parseIntArrayValue(JsonReader reader, String fieldName, List<String> errors)
748             throws IOException {
749         int initialErrorCount = errors.size();
750         var values = new IntArray();
751 
752         try {
753             int parsed = parseArrayEntry(reader, (int index) -> {
754                 if (isString(reader)) {
755                     values.add(parseConstantValue(nextStringAdvance(reader), errors));
756                     return;
757                 }
758                 try {
759                     values.add(nextIntAdvance(reader));
760                 } catch (Exception e) {
761                     errors.add(fieldName + " doesn't have a valid int value at index " + index + " "
762                             + e.getMessage());
763                 }
764             });
765             if (parsed < 0) {
766                 errors.add(fieldName + " doesn't have a valid JSONArray value.");
767                 return null;
768             }
769         } catch (IllegalArgumentException e) {
770             errors.add("Failed to parse field: " + fieldName + ", error: " + e.getMessage());
771             return null;
772         }
773 
774         if (errors.size() > initialErrorCount) {
775             return null;
776         }
777 
778         return values.toArray();
779     }
780 
781     /**
782      * Parses a long JSON array.
783      *
784      * @param reader The reader to parse.
785      * @param fieldName Field name of JSON object name/value mapping.
786      * @param errors The list to store all errors.
787      * @return a long array of default values, null if failed to parse.
788      */
789     @Nullable
parseLongArrayValue(JsonReader reader, String fieldName, List<String> errors)790     private long[] parseLongArrayValue(JsonReader reader, String fieldName, List<String> errors)
791             throws IOException {
792         int initialErrorCount = errors.size();
793         var values = new LongArray();
794 
795         try {
796             int parsed = parseArrayEntry(reader, (int index) -> {
797                 if (isString(reader)) {
798                     values.add(parseConstantValue(nextStringAdvance(reader), errors));
799                     return;
800                 }
801                 try {
802                     values.add(nextLongAdvance(reader));
803                 } catch (Exception e) {
804                     errors.add(fieldName + " doesn't have a valid long value at index " + index
805                             + " " + e.getMessage());
806                 }
807             });
808             if (parsed < 0) {
809                 errors.add(fieldName + " doesn't have a valid JSONArray value.");
810                 return null;
811             }
812         } catch (IllegalArgumentException e) {
813             errors.add("Failed to parse field: " + fieldName + ", error: " + e.getMessage());
814             return null;
815         }
816 
817         if (errors.size() > initialErrorCount) {
818             return null;
819         }
820 
821         return values.toArray();
822     }
823 
824     /**
825      * Parses a float JSON array.
826      *
827      * @param reader The reader to parse.
828      * @param fieldName Field name of JSON object name/value mapping.
829      * @param errors The list to store all errors.
830      * @return a float array of default value, null if failed to parse.
831      */
832     @Nullable
parseFloatArrayValue(JsonReader reader, String fieldName, List<String> errors)833     private float[] parseFloatArrayValue(JsonReader reader, String fieldName, List<String> errors)
834             throws IOException {
835         int initialErrorCount = errors.size();
836         List<Float> values = new ArrayList<>();
837 
838         try {
839             int parsed = parseArrayEntry(reader, (int index) -> {
840                 if (isString(reader)) {
841                     values.add((float) parseConstantValue(nextStringAdvance(reader), errors));
842                     return;
843                 }
844                 try {
845                     values.add((float) nextDoubleAdvance(reader));
846                 } catch (Exception e) {
847                     errors.add(fieldName + " doesn't have a valid float value at index " + index
848                             + " " + e.getMessage());
849                 }
850             });
851             if (parsed < 0) {
852                 errors.add(fieldName + " doesn't have a valid JSONArray value.");
853                 return null;
854             }
855         } catch (IllegalArgumentException e) {
856             errors.add("Failed to parse field: " + fieldName + ", error: " + e.getMessage());
857             return null;
858         }
859 
860         if (errors.size() > initialErrorCount) {
861             return null;
862         }
863         var valueArray = new float[values.size()];
864         for (int i = 0; i < values.size(); i++) {
865             valueArray[i] = values.get(i);
866         }
867         return valueArray;
868     }
869 
870     /**
871      * Checks if the next token is a string.
872      *
873      * @param reader The reader.
874      * @return {@code true} if the next token is a string.
875      */
isString(JsonReader reader)876     private boolean isString(JsonReader reader) throws IOException {
877         return reader.peek() == JsonToken.STRING;
878     }
879 
880     /**
881      * Checks if config file exists and has read permission.
882      *
883      * @param configFile Either default or custom config JSON file.
884      * @return A boolean value to determine if config file is valid.
885      */
isFileValid(File configFile)886     private boolean isFileValid(File configFile) {
887         return configFile.exists() && configFile.isFile();
888     }
889 
890     private interface RunanbleWithException {
run(String fieldName)891         void run(String fieldName) throws IOException;
892     }
893 
894     /**
895      * Iterates through entries in a JSONObject.
896      *
897      * If reader is not currently parsing an object, the cursor will still be advanced.
898      *
899      * @param reader The reader.
900      * @param forEachEntry The invokable for each entry.
901      * @return How many entries are iterated or -1 if the reader is not currently parsing an object.
902      */
parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)903     private static int parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)
904             throws IOException {
905         if (reader.peek() != JsonToken.BEGIN_OBJECT) {
906             reader.skipValue();
907             return -1;
908         }
909         int i = 0;
910         reader.beginObject();
911         while (reader.hasNext()) {
912             forEachEntry.run(reader.nextName());
913             i++;
914         }
915         reader.endObject();
916         return i;
917     }
918 
919     private interface RunanbleIndexWithException {
run(int index)920         void run(int index) throws IOException;
921     }
922 
923     /**
924      * Iterates through entries in a JSONArray.
925      *
926      * If reader is not currently parsing an array, the cursor will still be advanced.
927      *
928      * @param reader The reader.
929      * @param forEachEntry The invokable for each entry.
930      * @return How many entries are iterated or -1 if the reader is not currently parsing an array.
931      */
parseArrayEntry(JsonReader reader, RunanbleIndexWithException forEachEntry)932     private static int parseArrayEntry(JsonReader reader, RunanbleIndexWithException forEachEntry)
933             throws IOException {
934         if (reader.peek() != JsonToken.BEGIN_ARRAY) {
935             reader.skipValue();
936             return -1;
937         }
938         reader.beginArray();
939         int i = 0;
940         while (reader.hasNext()) {
941             forEachEntry.run(i++);
942         }
943         reader.endArray();
944         return i;
945     }
946 
nextIntAdvance(JsonReader reader)947     private static int nextIntAdvance(JsonReader reader) throws IOException {
948         try {
949             return reader.nextInt();
950         } catch (RuntimeException | IOException e) {
951             reader.skipValue();
952             throw e;
953         }
954     }
955 
nextLongAdvance(JsonReader reader)956     private static long nextLongAdvance(JsonReader reader) throws IOException {
957         try {
958             return reader.nextLong();
959         } catch (RuntimeException | IOException e) {
960             reader.skipValue();
961             throw e;
962         }
963     }
964 
nextDoubleAdvance(JsonReader reader)965     private static double nextDoubleAdvance(JsonReader reader) throws IOException {
966         try {
967             return reader.nextDouble();
968         } catch (RuntimeException | IOException e) {
969             reader.skipValue();
970             throw e;
971         }
972     }
973 
nextStringAdvance(JsonReader reader)974     private static String nextStringAdvance(JsonReader reader) throws IOException {
975         try {
976             return reader.nextString();
977         } catch (RuntimeException | IOException e) {
978             reader.skipValue();
979             throw e;
980         }
981     }
982 }
983