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