• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.VehicleArea;
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.hardware.automotive.vehicle.VehiclePropertyGroup;
39 import android.hardware.automotive.vehicle.VehiclePropertyType;
40 import android.util.Pair;
41 import android.util.SparseArray;
42 
43 import com.android.car.CarLog;
44 
45 import org.json.JSONArray;
46 import org.json.JSONException;
47 import org.json.JSONObject;
48 
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.IOException;
52 import java.io.InputStream;
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     private static final String ENUM_CLASS_DIRECTORY = "android.hardware.automotive.vehicle.";
66     private static final String JSON_FIELD_NAME_ROOT = "properties";
67     private static final String JSON_FIELD_NAME_PROPERTY_ID = "property";
68     private static final String JSON_FIELD_NAME_DEFAULT_VALUE = "defaultValue";
69     private static final String JSON_FIELD_NAME_AREAS = "areas";
70     private static final String JSON_FIELD_NAME_CONFIG_ARRAY = "configArray";
71     private static final String JSON_FIELD_NAME_CONFIG_STRING = "configString";
72     private static final String JSON_FIELD_NAME_MIN_SAMPLE_RATE = "minSampleRate";
73     private static final String JSON_FIELD_NAME_MAX_SAMPLE_RATE = "maxSampleRate";
74     private static final String JSON_FIELD_NAME_AREA_ID = "areaId";
75     private static final String JSON_FIELD_NAME_INT32_VALUES = "int32Values";
76     private static final String JSON_FIELD_NAME_INT64_VALUES = "int64Values";
77     private static final String JSON_FIELD_NAME_FLOAT_VALUES = "floatValues";
78     private static final String JSON_FIELD_NAME_STRING_VALUE = "stringValue";
79     private static final String JSON_FIELD_NAME_MIN_INT32_VALUE = "minInt32Value";
80     private static final String JSON_FIELD_NAME_MAX_INT32_VALUE = "maxInt32Value";
81     private static final String JSON_FIELD_NAME_MIN_FLOAT_VALUE = "minFloatValue";
82     private static final String JSON_FIELD_NAME_MAX_FLOAT_VALUE = "maxFloatValue";
83     private static final String JSON_FIELD_NAME_ACCESS = "access";
84     private static final String JSON_FIELD_NAME_CHANGE_MODE = "changeMode";
85     private static final String JSON_FIELD_NAME_COMMENT = "comment";
86     // Following values are defined in PropertyUtils.h file
87     // (hardware/interfaces/automotive/vehicle/aidl/impl/utils/common/include/PropertyUtils.h).
88     private static final int DOOR_1_RIGHT = VehicleAreaDoor.ROW_1_RIGHT;
89     private static final int DOOR_1_LEFT = VehicleAreaDoor.ROW_1_LEFT;
90     private static final int DOOR_2_RIGHT = VehicleAreaDoor.ROW_2_RIGHT;
91     private static final int DOOR_2_LEFT = VehicleAreaDoor.ROW_2_LEFT;
92     private static final int DOOR_REAR = VehicleAreaDoor.REAR;
93     private static final int VENDOR_EXTENSION_INT_PROPERTY = 0x103 | VehiclePropertyGroup.VENDOR
94                                 | VehiclePropertyType.INT32 | VehicleArea.WINDOW;
95     private static final int VENDOR_EXTENSION_BOOLEAN_PROPERTY = 0x101 | VehiclePropertyGroup.VENDOR
96                                 | VehiclePropertyType.BOOLEAN | VehicleArea.DOOR;
97     private static final int VENDOR_EXTENSION_STRING_PROPERTY = 0x104 | VehiclePropertyGroup.VENDOR
98                                 | VehiclePropertyType.STRING | VehicleArea.GLOBAL;
99     private static final int VENDOR_EXTENSION_FLOAT_PROPERTY = 0x102 | VehiclePropertyGroup.VENDOR
100                                 | VehiclePropertyType.FLOAT | VehicleArea.SEAT;
101     private static final int WINDOW_1_LEFT = VehicleAreaWindow.ROW_1_LEFT;
102     private static final int WINDOW_1_RIGHT = VehicleAreaWindow.ROW_1_RIGHT;
103     private static final int WINDOW_2_LEFT = VehicleAreaWindow.ROW_2_LEFT;
104     private static final int WINDOW_2_RIGHT = VehicleAreaWindow.ROW_2_RIGHT;
105     private static final int WINDOW_ROOF_TOP_1 = VehicleAreaWindow.ROOF_TOP_1;
106     private static final int SEAT_1_RIGHT = VehicleAreaSeat.ROW_1_RIGHT;
107     private static final int SEAT_1_LEFT = VehicleAreaSeat.ROW_1_LEFT;
108     private static final int SEAT_2_RIGHT = VehicleAreaSeat.ROW_2_RIGHT;
109     private static final int SEAT_2_LEFT = VehicleAreaSeat.ROW_2_LEFT;
110     private static final int SEAT_2_CENTER = VehicleAreaSeat.ROW_2_CENTER;
111     private static final int WHEEL_REAR_RIGHT = VehicleAreaWheel.RIGHT_REAR;
112     private static final int WHEEL_REAR_LEFT = VehicleAreaWheel.LEFT_REAR;
113     private static final int WHEEL_FRONT_RIGHT = VehicleAreaWheel.RIGHT_FRONT;
114     private static final int WHEEL_FRONT_LEFT = VehicleAreaWheel.LEFT_FRONT;
115     private static final int CHARGE_PORT_FRONT_LEFT = PortLocationType.FRONT_LEFT;
116     private static final int CHARGE_PORT_REAR_LEFT = PortLocationType.REAR_LEFT;
117     private static final int FAN_DIRECTION_UNKNOWN = VehicleHvacFanDirection.UNKNOWN;
118     private static final int FAN_DIRECTION_FLOOR = VehicleHvacFanDirection.FLOOR;
119     private static final int FAN_DIRECTION_FACE = VehicleHvacFanDirection.FACE;
120     private static final int FAN_DIRECTION_DEFROST = VehicleHvacFanDirection.DEFROST;
121     private static final int FUEL_DOOR_REAR_LEFT = PortLocationType.REAR_LEFT;
122     // TODO(b/241984846) Keep SEAT_2_CENTER from HVAC_LEFT here. May have a new design to handle
123     //  HVAC zone ids.
124     private static final int HVAC_LEFT = SEAT_1_LEFT | SEAT_2_LEFT | SEAT_2_CENTER;
125     private static final int HVAC_RIGHT = SEAT_1_RIGHT | SEAT_2_RIGHT;
126     private static final int HVAC_ALL = HVAC_LEFT | HVAC_RIGHT;
127     private static final int LIGHT_STATE_ON = VehicleLightState.ON;
128     private static final int LIGHT_STATE_OFF = VehicleLightState.OFF;
129     private static final int LIGHT_SWITCH_ON = VehicleLightSwitch.ON;
130     private static final int LIGHT_SWITCH_OFF = VehicleLightSwitch.OFF;
131     private static final int LIGHT_SWITCH_AUTO = VehicleLightSwitch.AUTOMATIC;
132     private static final int EV_STOPPING_MODE_CREEP = EvStoppingMode.CREEP;
133     private static final int EV_STOPPING_MODE_ROLL = EvStoppingMode.ROLL;
134     private static final int EV_STOPPING_MODE_HOLD = EvStoppingMode.HOLD;
135     private static final int MIRROR_DRIVER_LEFT_RIGHT = VehicleAreaMirror.DRIVER_LEFT
136                                 | VehicleAreaMirror.DRIVER_RIGHT;
137     // Following are the test properties whose values are copying from TestPropertyUtils.h file(
138     // hardware/interfaces/automotive/vehicle/aidl/impl/utils/test/include/TestPropertyUtils.h).
139     private static final int ECHO_REVERSE_BYTES = 0x2a12 | VehiclePropertyGroup.VENDOR
140                                 | VehicleArea.GLOBAL | VehiclePropertyType.BYTES;
141     private static final int VENDOR_PROPERTY_ID = 0x2a13 | VehiclePropertyGroup.VENDOR
142                                 | VehicleArea.GLOBAL | VehiclePropertyType.INT32;
143     private static final int K_MIXED_TYPE_PROPERTY_FOR_TEST = 0x1111 | VehiclePropertyGroup.VENDOR
144                                 | VehicleArea.GLOBAL | VehiclePropertyType.MIXED;
145     private static final int VENDOR_CLUSTER_NAVIGATION_STATE = toVendorProperty(
146                                 VehicleProperty.CLUSTER_NAVIGATION_STATE);
147     private static final int VENDOR_CLUSTER_REQUEST_DISPLAY = toVendorProperty(
148                                 VehicleProperty.CLUSTER_REQUEST_DISPLAY);
149     private static final int VENDOR_CLUSTER_SWITCH_UI = toVendorProperty(
150                                 VehicleProperty.CLUSTER_SWITCH_UI);
151     private static final int VENDOR_CLUSTER_DISPLAY_STATE = toVendorProperty(
152                                 VehicleProperty.CLUSTER_DISPLAY_STATE);
153     private static final int VENDOR_CLUSTER_REPORT_STATE = toVendorProperty(
154                                 VehicleProperty.CLUSTER_REPORT_STATE);
155     private static final int PLACEHOLDER_PROPERTY_INT = 0x2a11 | VehiclePropertyGroup.VENDOR
156                                 | VehicleArea.GLOBAL | VehiclePropertyType.INT32;
157     private static final int PLACEHOLDER_PROPERTY_FLOAT = 0x2a11 | VehiclePropertyGroup.VENDOR
158                                 | VehicleArea.GLOBAL | VehiclePropertyType.FLOAT;
159     private static final int PLACEHOLDER_PROPERTY_BOOLEAN = 0x2a11 | VehiclePropertyGroup.VENDOR
160                                 | VehicleArea.GLOBAL | VehiclePropertyType.BOOLEAN;
161     private static final int PLACEHOLDER_PROPERTY_STRING = 0x2a11 | VehiclePropertyGroup.VENDOR
162                                 | VehicleArea.GLOBAL | VehiclePropertyType.STRING;
163     private static final Map<String, Integer> CONSTANTS_BY_NAME = Map.ofEntries(
164             Map.entry("DOOR_1_RIGHT", DOOR_1_RIGHT),
165             Map.entry("DOOR_1_LEFT", DOOR_1_LEFT),
166             Map.entry("DOOR_2_RIGHT", DOOR_2_RIGHT),
167             Map.entry("DOOR_2_LEFT", DOOR_2_LEFT),
168             Map.entry("DOOR_REAR", DOOR_REAR),
169             Map.entry("HVAC_ALL", HVAC_ALL),
170             Map.entry("HVAC_LEFT", HVAC_LEFT),
171             Map.entry("HVAC_RIGHT", HVAC_RIGHT),
172             Map.entry("VENDOR_EXTENSION_INT_PROPERTY", VENDOR_EXTENSION_INT_PROPERTY),
173             Map.entry("VENDOR_EXTENSION_BOOLEAN_PROPERTY", VENDOR_EXTENSION_BOOLEAN_PROPERTY),
174             Map.entry("VENDOR_EXTENSION_STRING_PROPERTY", VENDOR_EXTENSION_STRING_PROPERTY),
175             Map.entry("VENDOR_EXTENSION_FLOAT_PROPERTY", VENDOR_EXTENSION_FLOAT_PROPERTY),
176             Map.entry("WINDOW_1_LEFT", WINDOW_1_LEFT),
177             Map.entry("WINDOW_1_RIGHT", WINDOW_1_RIGHT),
178             Map.entry("WINDOW_2_LEFT", WINDOW_2_LEFT),
179             Map.entry("WINDOW_2_RIGHT", WINDOW_2_RIGHT),
180             Map.entry("WINDOW_ROOF_TOP_1", WINDOW_ROOF_TOP_1),
181             Map.entry("WINDOW_1_RIGHT_2_LEFT_2_RIGHT", WINDOW_1_RIGHT | WINDOW_2_LEFT
182                     | WINDOW_2_RIGHT),
183             Map.entry("SEAT_1_LEFT", SEAT_1_LEFT),
184             Map.entry("SEAT_1_RIGHT", SEAT_1_RIGHT),
185             Map.entry("SEAT_2_LEFT", SEAT_2_LEFT),
186             Map.entry("SEAT_2_RIGHT", SEAT_2_RIGHT),
187             Map.entry("SEAT_2_CENTER", SEAT_2_CENTER),
188             Map.entry("SEAT_2_LEFT_2_RIGHT_2_CENTER", SEAT_2_LEFT | SEAT_2_RIGHT | SEAT_2_CENTER),
189             Map.entry("WHEEL_REAR_RIGHT", WHEEL_REAR_RIGHT),
190             Map.entry("WHEEL_REAR_LEFT", WHEEL_REAR_LEFT),
191             Map.entry("WHEEL_FRONT_RIGHT", WHEEL_FRONT_RIGHT),
192             Map.entry("WHEEL_FRONT_LEFT", WHEEL_FRONT_LEFT),
193             Map.entry("CHARGE_PORT_FRONT_LEFT", CHARGE_PORT_FRONT_LEFT),
194             Map.entry("CHARGE_PORT_REAR_LEFT", CHARGE_PORT_REAR_LEFT),
195             Map.entry("FAN_DIRECTION_UNKNOWN", FAN_DIRECTION_UNKNOWN),
196             Map.entry("FAN_DIRECTION_FLOOR", FAN_DIRECTION_FLOOR),
197             Map.entry("FAN_DIRECTION_FACE", FAN_DIRECTION_FACE),
198             Map.entry("FAN_DIRECTION_DEFROST", FAN_DIRECTION_DEFROST),
199             Map.entry("FAN_DIRECTION_FACE_FLOOR", FAN_DIRECTION_FACE | FAN_DIRECTION_FLOOR),
200             Map.entry("FAN_DIRECTION_FACE_DEFROST", FAN_DIRECTION_FACE | FAN_DIRECTION_DEFROST),
201             Map.entry("FAN_DIRECTION_FLOOR_DEFROST", FAN_DIRECTION_FLOOR | FAN_DIRECTION_DEFROST),
202             Map.entry("FAN_DIRECTION_FLOOR_DEFROST_FACE", FAN_DIRECTION_FLOOR
203                     | FAN_DIRECTION_DEFROST | FAN_DIRECTION_FACE),
204             Map.entry("FUEL_DOOR_REAR_LEFT", FUEL_DOOR_REAR_LEFT),
205             Map.entry("LIGHT_STATE_ON", LIGHT_STATE_ON),
206             Map.entry("LIGHT_STATE_OFF", LIGHT_STATE_OFF),
207             Map.entry("LIGHT_SWITCH_ON", LIGHT_SWITCH_ON),
208             Map.entry("LIGHT_SWITCH_OFF", LIGHT_SWITCH_OFF),
209             Map.entry("LIGHT_SWITCH_AUTO", LIGHT_SWITCH_AUTO),
210             Map.entry("EV_STOPPING_MODE_CREEP", EV_STOPPING_MODE_CREEP),
211             Map.entry("EV_STOPPING_MODE_ROLL", EV_STOPPING_MODE_ROLL),
212             Map.entry("EV_STOPPING_MODE_HOLD", EV_STOPPING_MODE_HOLD),
213             Map.entry("MIRROR_DRIVER_LEFT_RIGHT", MIRROR_DRIVER_LEFT_RIGHT),
214             Map.entry("ECHO_REVERSE_BYTES", ECHO_REVERSE_BYTES),
215             Map.entry("VENDOR_PROPERTY_ID", VENDOR_PROPERTY_ID),
216             Map.entry("kMixedTypePropertyForTest", K_MIXED_TYPE_PROPERTY_FOR_TEST),
217             Map.entry("VENDOR_CLUSTER_NAVIGATION_STATE", VENDOR_CLUSTER_NAVIGATION_STATE),
218             Map.entry("VENDOR_CLUSTER_REQUEST_DISPLAY", VENDOR_CLUSTER_REQUEST_DISPLAY),
219             Map.entry("VENDOR_CLUSTER_SWITCH_UI", VENDOR_CLUSTER_SWITCH_UI),
220             Map.entry("VENDOR_CLUSTER_DISPLAY_STATE", VENDOR_CLUSTER_DISPLAY_STATE),
221             Map.entry("VENDOR_CLUSTER_REPORT_STATE", VENDOR_CLUSTER_REPORT_STATE),
222             Map.entry("PLACEHOLDER_PROPERTY_INT", PLACEHOLDER_PROPERTY_INT),
223             Map.entry("PLACEHOLDER_PROPERTY_FLOAT", PLACEHOLDER_PROPERTY_FLOAT),
224             Map.entry("PLACEHOLDER_PROPERTY_BOOLEAN", PLACEHOLDER_PROPERTY_BOOLEAN),
225             Map.entry("PLACEHOLDER_PROPERTY_STRING", PLACEHOLDER_PROPERTY_STRING)
226     );
227 
228     /**
229      * Reads custom config files and parses the JSON root object whose field name is "properties".
230      *
231      * @param customConfigFile The custom config JSON file to parse from.
232      * @return a list of {@link ConfigDeclaration} storing configs and values for each property.
233      * @throws IOException if unable to read the config file.
234      * @throws IllegalArgumentException if file is invalid JSON or when a JSONException is caught.
235      */
parseJsonConfig(File customConfigFile)236     public SparseArray<ConfigDeclaration> parseJsonConfig(File customConfigFile) throws
237             IOException, IllegalArgumentException {
238         // Check if config file exists.
239         if (!isFileValid(customConfigFile)) {
240             Slogf.w(TAG, "Custom config file: %s is not a valid file.", customConfigFile.getPath());
241             return new SparseArray<>(/* initialCapacity= */ 0);
242         }
243 
244         FileInputStream customConfigFileStream = new FileInputStream(customConfigFile);
245         if (customConfigFileStream.available() == 0) {
246             Slogf.w(TAG, "Custom config file: %s is empty.", customConfigFile.getPath());
247             return new SparseArray<>(/* initialCapacity= */ 0);
248         }
249         return parseJsonConfig(customConfigFileStream);
250     }
251 
252     /**
253      * Reads default config file from java resources which is in the type of input stream.
254      *
255      * @param configInputStream The {@link InputStream} to parse from.
256      * @return a list of {@link ConfigDeclaration} storing configs and values for each property.
257      * @throws IOException if unable to read the config file.
258      * @throws IllegalArgumentException if file is invalid JSON or when a JSONException is caught.
259      */
parseJsonConfig(InputStream configInputStream)260     public SparseArray<ConfigDeclaration> parseJsonConfig(InputStream configInputStream)
261             throws IOException {
262         String configString = new String(configInputStream.readAllBytes());
263 
264         // Parse JSON root object.
265         JSONObject configJsonObject;
266         JSONArray configJsonArray;
267         try {
268             configJsonObject = new JSONObject(configString);
269         } catch (JSONException e) {
270             throw new IllegalArgumentException("This file does not contain a valid JSONObject.", e);
271         }
272         try {
273             configJsonArray = configJsonObject.getJSONArray(JSON_FIELD_NAME_ROOT);
274         } catch (JSONException e) {
275             throw new IllegalArgumentException(JSON_FIELD_NAME_ROOT + " field value is not a valid "
276                 + "JSONArray.", e);
277         }
278 
279         SparseArray<ConfigDeclaration> allPropConfigs = new SparseArray<>();
280         List<String> errors = new ArrayList<>();
281         // Parse each property.
282         for (int i = 0; i < configJsonArray.length(); i++) {
283             JSONObject propertyObject = configJsonArray.optJSONObject(i);
284             if (propertyObject == null) {
285                 errors.add(JSON_FIELD_NAME_ROOT + " array has an invalid JSON element at index "
286                         + i);
287                 continue;
288             }
289             ConfigDeclaration propConfig = parseEachProperty(propertyObject, errors);
290             if (propConfig == null) {
291                 errors.add("Unable to parse JSON object: " + propertyObject + " at index " + i);
292                 if (allPropConfigs.size() != 0) {
293                     errors.add("Last successfully parsed property Id: "
294                             + allPropConfigs.valueAt(allPropConfigs.size() - 1).getConfig().prop);
295                 }
296                 continue;
297             }
298             allPropConfigs.put(propConfig.getConfig().prop, propConfig);
299         }
300         if (!errors.isEmpty()) {
301             throw new IllegalArgumentException(String.join("\n", errors));
302         }
303         return allPropConfigs;
304     }
305 
306     /**
307      * Parses each property for its configs and values.
308      *
309      * @param propertyObject A JSONObject which stores all configs and values of a property.
310      * @param errors A list to keep all errors.
311      * @return a {@link ConfigDeclaration} instance, null if failed to parse.
312      */
313     @Nullable
parseEachProperty(JSONObject propertyObject, List<String> errors)314     private ConfigDeclaration parseEachProperty(JSONObject propertyObject, List<String> errors) {
315         int initialErrorCount = errors.size();
316         List<String> fieldNames = getFieldNames(propertyObject);
317 
318         if (fieldNames == null) {
319             errors.add("The JSONObject " + propertyObject + " is empty.");
320             return null;
321         }
322 
323         VehiclePropConfig vehiclePropConfig = new VehiclePropConfig();
324         vehiclePropConfig.prop = VehicleProperty.INVALID;
325         boolean isAccessSet = false;
326         boolean isChangeModeSet = false;
327         List<VehicleAreaConfig> areaConfigs = new ArrayList<>();
328         RawPropValues rawPropValues = null;
329         SparseArray<RawPropValues> defaultValuesByAreaId = new SparseArray<>();
330 
331         for (int i = 0; i < fieldNames.size(); i++) {
332             String fieldName = fieldNames.get(i);
333             switch (fieldName) {
334                 case JSON_FIELD_NAME_PROPERTY_ID:
335                     vehiclePropConfig.prop = parseIntValue(propertyObject, fieldName, errors);
336                     break;
337                 case JSON_FIELD_NAME_CONFIG_STRING:
338                     vehiclePropConfig.configString = parseStringValue(propertyObject, fieldName,
339                         errors);
340                     break;
341                 case JSON_FIELD_NAME_MIN_SAMPLE_RATE:
342                     vehiclePropConfig.minSampleRate = parseFloatValue(propertyObject, fieldName,
343                         errors);
344                     break;
345                 case JSON_FIELD_NAME_MAX_SAMPLE_RATE:
346                     vehiclePropConfig.maxSampleRate = parseFloatValue(propertyObject, fieldName,
347                         errors);
348                     break;
349                 case JSON_FIELD_NAME_ACCESS:
350                     vehiclePropConfig.access = parseIntValue(propertyObject, fieldName, errors);
351                     isAccessSet = true;
352                     break;
353                 case JSON_FIELD_NAME_CHANGE_MODE:
354                     vehiclePropConfig.changeMode = parseIntValue(propertyObject, fieldName, errors);
355                     isChangeModeSet = true;
356                     break;
357                 case JSON_FIELD_NAME_CONFIG_ARRAY:
358                     JSONArray configArray = propertyObject.optJSONArray(fieldName);
359                     if (configArray == null) {
360                         errors.add(fieldName + " doesn't have a mapped JSONArray value.");
361                         continue;
362                     }
363                     vehiclePropConfig.configArray = parseIntArrayValue(configArray, errors);
364                     break;
365                 case JSON_FIELD_NAME_DEFAULT_VALUE:
366                     JSONObject defaultValueObject = propertyObject.optJSONObject(fieldName);
367                     if (defaultValueObject == null) {
368                         Slogf.w(TAG, "%s doesn't have a mapped value.", fieldName);
369                         continue;
370                     }
371                     rawPropValues = parseDefaultValue(defaultValueObject, errors);
372                     break;
373                 case JSON_FIELD_NAME_AREAS:
374                     JSONArray areas = propertyObject.optJSONArray(fieldName);
375                     if (areas == null) {
376                         errors.add(fieldName + " doesn't have a mapped array value.");
377                         continue;
378                     }
379                     for (int j = 0; j < areas.length(); j++) {
380                         JSONObject areaObject = areas.optJSONObject(j);
381                         if (areaObject == null) {
382                             errors.add("Unable to get a JSONObject element for " + fieldName
383                                     + " at index " + j);
384                             continue;
385                         }
386                         Pair<VehicleAreaConfig, RawPropValues> result =
387                                 parseAreaConfig(areaObject, errors);
388                         if (result != null) {
389                             areaConfigs.add(result.first);
390                             if (result.second != null) {
391                                 defaultValuesByAreaId.put(result.first.areaId, result.second);
392                             }
393                         }
394                     }
395                     vehiclePropConfig.areaConfigs = areaConfigs.toArray(new VehicleAreaConfig[0]);
396                     break;
397                 case JSON_FIELD_NAME_COMMENT:
398                     // The "comment" field is used for comment in the config files and is ignored
399                     // by the parser.
400                     break;
401                 default:
402                     Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
403             }
404         }
405 
406         if (vehiclePropConfig.prop == VehicleProperty.INVALID) {
407             errors.add(propertyObject + " doesn't have propId. PropId is required.");
408             return null;
409         }
410 
411         if (errors.size() > initialErrorCount) {
412             return null;
413         }
414 
415         if (!isAccessSet) {
416             if (AccessForVehicleProperty.values.containsKey(vehiclePropConfig.prop)) {
417                 vehiclePropConfig.access =
418                         AccessForVehicleProperty.values.get(vehiclePropConfig.prop);
419             } else {
420                 errors.add("Access field is not set for this property: " + propertyObject);
421             }
422         }
423 
424         if (!isChangeModeSet) {
425             if (ChangeModeForVehicleProperty.values.containsKey(vehiclePropConfig.prop)) {
426                 vehiclePropConfig.changeMode = ChangeModeForVehicleProperty.values
427                         .get(vehiclePropConfig.prop);
428             } else {
429                 errors.add("ChangeMode field is not set for this property: " + propertyObject);
430             }
431         }
432 
433         return new ConfigDeclaration(vehiclePropConfig, rawPropValues, defaultValuesByAreaId);
434     }
435 
436     /**
437      * Parses area JSON config object.
438      *
439      * @param areaObject A JSONObject of field name "areas".
440      * @param errors The list to store all errors.
441      * @return a pair of configs and values for one area, null if failed to parse.
442      */
443     @Nullable
parseAreaConfig(JSONObject areaObject, List<String> errors)444     private Pair<VehicleAreaConfig, RawPropValues> parseAreaConfig(JSONObject areaObject,
445             List<String> errors) {
446         int initialErrorCount = errors.size();
447         List<String> fieldNames = getFieldNames(areaObject);
448 
449         if (fieldNames == null) {
450             errors.add("The JSONObject " + areaObject + " is empty.");
451             return null;
452         }
453 
454         VehicleAreaConfig areaConfig = new VehicleAreaConfig();
455         RawPropValues defaultValue = null;
456         boolean hasAreaId = false;
457 
458         for (int i = 0; i < fieldNames.size(); i++) {
459             String fieldName = fieldNames.get(i);
460             switch (fieldName) {
461                 case JSON_FIELD_NAME_AREA_ID:
462                     areaConfig.areaId = parseIntValue(areaObject, fieldName, errors);
463                     hasAreaId = true;
464                     break;
465                 case JSON_FIELD_NAME_MIN_INT32_VALUE:
466                     areaConfig.minInt32Value = parseIntValue(areaObject, fieldName, errors);
467                     break;
468                 case JSON_FIELD_NAME_MAX_INT32_VALUE:
469                     areaConfig.maxInt32Value = parseIntValue(areaObject, fieldName, errors);
470                     break;
471                 case JSON_FIELD_NAME_MIN_FLOAT_VALUE:
472                     areaConfig.minFloatValue = parseFloatValue(areaObject, fieldName, errors);
473                     break;
474                 case JSON_FIELD_NAME_MAX_FLOAT_VALUE:
475                     areaConfig.maxFloatValue = parseFloatValue(areaObject, fieldName, errors);
476                     break;
477                 case JSON_FIELD_NAME_DEFAULT_VALUE:
478                     defaultValue = parseDefaultValue(areaObject.optJSONObject(fieldName), errors);
479                     break;
480                 default:
481                     Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
482             }
483         }
484 
485         if (!hasAreaId) {
486             errors.add(areaObject + " doesn't have areaId. AreaId is required.");
487             return null;
488         }
489 
490         if (errors.size() > initialErrorCount) {
491             return null;
492         }
493 
494         return Pair.create(areaConfig, defaultValue);
495     }
496 
497     /**
498      * Parses the "defaultValue" field of a property object and area property object.
499      *
500      * @param defaultValue The defaultValue JSONObject to be parsed.
501      * @param errors The list to store all errors.
502      * @return a {@link RawPropValues} object which stores defaultValue, null if failed to parse.
503      */
504     @Nullable
parseDefaultValue(JSONObject defaultValue, List<String> errors)505     private RawPropValues parseDefaultValue(JSONObject defaultValue, List<String> errors) {
506         int initialErrorCount = errors.size();
507         List<String> fieldNames = getFieldNames(defaultValue);
508 
509         if (fieldNames == null) {
510             Slogf.w(TAG, "The JSONObject %s is empty.", defaultValue.toString());
511             return null;
512         }
513 
514         RawPropValues rawPropValues = new RawPropValues();
515 
516         for (int i = 0; i < fieldNames.size(); i++) {
517             String fieldName = fieldNames.get(i);
518             switch (fieldName) {
519                 case JSON_FIELD_NAME_INT32_VALUES: {
520                     JSONArray int32Values = defaultValue.optJSONArray(fieldName);
521                     if (int32Values == null) {
522                         errors.add("Failed to parse the field name: " + fieldName + " for "
523                                 + "defaultValueObject: " + defaultValue);
524                         continue;
525                     }
526                     rawPropValues.int32Values = parseIntArrayValue(int32Values, errors);
527                     break;
528                 }
529                 case JSON_FIELD_NAME_INT64_VALUES: {
530                     JSONArray int64Values = defaultValue.optJSONArray(fieldName);
531                     if (int64Values == null) {
532                         errors.add("Failed to parse the field name: " + fieldName + " for "
533                                 + "defaultValueObject: " + defaultValue);
534                         continue;
535                     }
536                     rawPropValues.int64Values = parseLongArrayValue(int64Values, errors);
537                     break;
538                 }
539                 case JSON_FIELD_NAME_FLOAT_VALUES: {
540                     JSONArray floatValues = defaultValue.optJSONArray(fieldName);
541                     if (floatValues == null) {
542                         errors.add("Failed to parse the field name: " + fieldName + " for "
543                                 + "defaultValueObject: " + defaultValue);
544                         continue;
545                     }
546                     rawPropValues.floatValues = parseFloatArrayValue(floatValues, errors);
547                     break;
548                 }
549                 case JSON_FIELD_NAME_STRING_VALUE: {
550                     rawPropValues.stringValue = parseStringValue(defaultValue, fieldName, errors);
551                     break;
552                 }
553                 default:
554                     Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
555             }
556         }
557 
558         if (errors.size() > initialErrorCount) {
559             return null;
560         }
561         return rawPropValues;
562     }
563 
564     /**
565      * Parses String Json value.
566      *
567      * @param parentObject The JSONObject will be parsed.
568      * @param fieldName Field name of JSON object name/value mapping.
569      * @param errors The list to store all errors.
570      * @return a string of parsed value, null if failed to parse.
571      */
572     @Nullable
parseStringValue(JSONObject parentObject, String fieldName, List<String> errors)573     private String parseStringValue(JSONObject parentObject, String fieldName,
574             List<String> errors) {
575         String value = parentObject.optString(fieldName);
576         if (Objects.equals(value, "")) {
577             errors.add(fieldName + " doesn't have a mapped value.");
578             return null;
579         }
580         return value;
581     }
582 
583     /**
584      * Parses int Json value.
585      *
586      * @param parentObject The JSONObject will be parsed.
587      * @param fieldName Field name of JSON object name/value mapping.
588      * @param errors The list to store all errors.
589      * @return a value as int, 0 if failed to parse.
590      */
parseIntValue(JSONObject parentObject, String fieldName, List<String> errors)591     private int parseIntValue(JSONObject parentObject, String fieldName, List<String> errors) {
592         if (isString(parentObject, fieldName)) {
593             String constantValue;
594             try {
595                 constantValue = parentObject.getString(fieldName);
596                 return parseConstantValue(constantValue, errors);
597             } catch (JSONException e) {
598                 errors.add(fieldName + " doesn't have a mapped string value. " + e.getMessage());
599                 return 0;
600             }
601         }
602         Object value = parentObject.opt(fieldName);
603         if (value != JSONObject.NULL) {
604             if (value.getClass() == Integer.class) {
605                 return parentObject.optInt(fieldName);
606             }
607             errors.add(fieldName + " doesn't have a mapped int value.");
608             return 0;
609         }
610         errors.add(fieldName + " doesn't have a mapped value.");
611         return 0;
612     }
613 
614     /**
615      * Parses float Json value.
616      *
617      * @param parentObject The JSONObject will be parsed.
618      * @param fieldName Field name of JSON object name/value mapping.
619      * @param errors The list to store all errors.
620      * @return the parsed value as float, {@code 0f} if failed to parse.
621      */
parseFloatValue(JSONObject parentObject, String fieldName, List<String> errors)622     private float parseFloatValue(JSONObject parentObject, String fieldName, List<String> errors) {
623         if (isString(parentObject, fieldName)) {
624             String constantValue;
625             try {
626                 constantValue = parentObject.getString(fieldName);
627             } catch (JSONException e) {
628                 errors.add(fieldName + " doesn't have a mapped string value. " + e.getMessage());
629                 return 0f;
630             }
631             return (float) parseConstantValue(constantValue, errors);
632         }
633         try {
634             return (float) parentObject.getDouble(fieldName);
635         } catch (JSONException e) {
636             errors.add(fieldName + " doesn't have a mapped float value. " + e.getMessage());
637             return 0f;
638         }
639     }
640 
641     /**
642      * Parses enum class constant.
643      *
644      * @param stringValue The constant string to be parsed.
645      * @param errors A list to keep all errors.
646      * @return the int value of an enum constant, 0 if the constant format is invalid.
647      */
parseConstantValue(String stringValue, List<String> errors)648     private int parseConstantValue(String stringValue, List<String> errors) {
649         String[] propIdStrings = stringValue.split("::");
650         if (propIdStrings.length != 2 || propIdStrings[0].isEmpty() || propIdStrings[1].isEmpty()) {
651             errors.add(stringValue + " must in the form of <EnumClassName>::<ConstantName>.");
652             return 0;
653         }
654         String enumClassName = ENUM_CLASS_DIRECTORY + propIdStrings[0];
655         String constantName = propIdStrings[1];
656 
657         if (Objects.equals(propIdStrings[0], "Constants")) {
658             if (CONSTANTS_BY_NAME.containsKey(constantName)) {
659                 return CONSTANTS_BY_NAME.get(constantName);
660             }
661             errors.add(constantName + " is not a valid constant name.");
662             return 0;
663         }
664 
665         Class enumClass;
666         try {
667             enumClass = Class.forName(enumClassName);
668         } catch (ClassNotFoundException e) {
669             errors.add(enumClassName + " is not a valid class name. " + e.getMessage());
670             return 0;
671         }
672         Field[] fields = enumClass.getDeclaredFields();
673         for (Field field : fields) {
674             if (constantName.equals(field.getName())) {
675                 try {
676                     return field.getInt(enumClass);
677                 } catch (Exception e) {
678                     errors.add("Failed to get int value of " + enumClass + "." + constantName
679                             + " " + e.getMessage());
680                     return 0;
681                 }
682             }
683         }
684         errors.add(enumClass + " doesn't have a constant field with name " + constantName);
685         return 0;
686     }
687 
688     /**
689      * Parses int values in a {@link JSONArray}.
690      *
691      * @param values The JSON array to be parsed.
692      * @param errors The list to store all errors.
693      * @return an int array of default values, null if failed to parse.
694      */
695     @Nullable
parseIntArrayValue(JSONArray values, List<String> errors)696     private int[] parseIntArrayValue(JSONArray values, List<String> errors) {
697         int initialErrorCount = errors.size();
698         int[] valueArray = new int[values.length()];
699 
700         for (int i = 0; i < values.length(); i++) {
701             if (isString(values, i)) {
702                 String stringValue = values.optString(i);
703                 valueArray[i] = parseConstantValue(stringValue, errors);
704             } else {
705                 try {
706                     valueArray[i] = values.getInt(i);
707                 } catch (JSONException e) {
708                     errors.add(values + " doesn't have a mapped int value at index " + i + " "
709                             + e.getMessage());
710                 }
711             }
712         }
713 
714         if (errors.size() > initialErrorCount) {
715             return null;
716         }
717 
718         return valueArray;
719     }
720 
721     /**
722      * Parses long values in a {@link JSONArray}.
723      *
724      * @param values The JSON array to be parsed.
725      * @param errors The list to store all errors.
726      * @return a long array of default values, null if failed to parse.
727      */
728     @Nullable
parseLongArrayValue(JSONArray values, List<String> errors)729     private long[] parseLongArrayValue(JSONArray values, List<String> errors) {
730         int initialErrorCount = errors.size();
731         long[] valueArray = new long[values.length()];
732 
733         for (int i = 0; i < values.length(); i++) {
734             if (isString(values, i)) {
735                 String stringValue = values.optString(i);
736                 valueArray[i] = parseConstantValue(stringValue, errors);
737             } else {
738                 try {
739                     valueArray[i] = values.getLong(i);
740                 } catch (JSONException e) {
741                     errors.add(values + " doesn't have a mapped long value at index " + i + " "
742                             + e.getMessage());
743                 }
744             }
745         }
746 
747         if (errors.size() > initialErrorCount) {
748             return null;
749         }
750 
751         return valueArray;
752     }
753 
754     /**
755      * Parses float values in a {@link JSONArray}.
756      *
757      * @param values The JSON array to be parsed.
758      * @param errors The list to store all errors.
759      * @return a float array of default value, null if failed to parse.
760      */
761     @Nullable
parseFloatArrayValue(JSONArray values, List<String> errors)762     private float[] parseFloatArrayValue(JSONArray values, List<String> errors) {
763         int initialErrorCount = errors.size();
764         float[] valueArray = new float[values.length()];
765 
766         for (int i = 0; i < values.length(); i++) {
767             if (isString(values, i)) {
768                 String stringValue = values.optString(i);
769                 valueArray[i] = (float) parseConstantValue(stringValue, errors);
770             } else {
771                 try {
772                     valueArray[i] = (float) values.getDouble(i);
773                 } catch (JSONException e) {
774                     errors.add(values + " doesn't have a mapped float value at index " + i + " "
775                             + e.getMessage());
776                 }
777             }
778         }
779 
780         if (errors.size() > initialErrorCount) {
781             return null;
782         }
783         return valueArray;
784     }
785 
786     /**
787      * Checks if parentObject contains field and the field is a String.
788      *
789      * @param parentObject The JSONObject containing the field.
790      * @param fieldName The name for the JSON field.
791      * @return {@code true} if parent object contains this field and the value is string.
792      */
isString(JSONObject parentObject, String fieldName)793     private boolean isString(JSONObject parentObject, String fieldName) {
794         return parentObject.opt(fieldName) != JSONObject.NULL && parentObject.opt(fieldName)
795                 .getClass() == String.class;
796     }
797 
798     /**
799      * Checks if the JSON array contains the index and the element at the index is a String.
800      *
801      * @param jsonArray The JSON array to be checked.
802      * @param index The index of the JSON array element which will be checked.
803      * @return {@code true} if the JSON array has value at index and the value is string.
804      */
isString(JSONArray jsonArray, int index)805     private boolean isString(JSONArray jsonArray, int index) {
806         return jsonArray.opt(index) != JSONObject.NULL && jsonArray.opt(index).getClass()
807                 == String.class;
808     }
809 
810     /**
811      * Gets all field names of a {@link JSONObject}.
812      *
813      * @param jsonObject The JSON object to read field names from.
814      * @return a list of all the field names of an JSONObject, null if the object is empty.
815      */
816     @Nullable
getFieldNames(JSONObject jsonObject)817     private List<String> getFieldNames(JSONObject jsonObject) {
818         JSONArray names = jsonObject.names();
819 
820         if (names == null) {
821             return null;
822         }
823 
824         List<String> fieldNames = new ArrayList<>();
825         for (int i = 0; i < names.length(); i++) {
826             String fieldName = names.optString(i);
827             if (fieldName != null) {
828                 fieldNames.add(fieldName);
829             }
830         }
831         return fieldNames;
832     }
833 
834     /**
835      * Checks if config file exists and has read permission.
836      *
837      * @param configFile Either default or custom config JSON file.
838      * @return A boolean value to determine if config file is valid.
839      */
isFileValid(File configFile)840     private boolean isFileValid(File configFile) {
841         return configFile.exists() && configFile.isFile();
842     }
843 
844     /**
845      * Converts system property to vendor property.
846      *
847      * @param property The property going to be converted.
848      * @return an int represents vendor property.
849      */
toVendorProperty(int property)850     private static int toVendorProperty(int property) {
851         return (property & VehiclePropertyGroup.MASK) | VehiclePropertyGroup.VENDOR;
852     }
853 }
854