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