blob: 260b43e354213d01814bc7a02fa9a7a1c2449e13 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.hal.fakevhal;
import android.annotation.Nullable;
import android.car.builtin.util.Slogf;
import android.hardware.automotive.vehicle.AccessForVehicleProperty;
import android.hardware.automotive.vehicle.ChangeModeForVehicleProperty;
import android.hardware.automotive.vehicle.PortLocationType;
import android.hardware.automotive.vehicle.RawPropValues;
import android.hardware.automotive.vehicle.VehicleArea;
import android.hardware.automotive.vehicle.VehicleAreaConfig;
import android.hardware.automotive.vehicle.VehicleAreaDoor;
import android.hardware.automotive.vehicle.VehicleAreaMirror;
import android.hardware.automotive.vehicle.VehicleAreaSeat;
import android.hardware.automotive.vehicle.VehicleAreaWheel;
import android.hardware.automotive.vehicle.VehicleAreaWindow;
import android.hardware.automotive.vehicle.VehicleHvacFanDirection;
import android.hardware.automotive.vehicle.VehicleLightState;
import android.hardware.automotive.vehicle.VehicleLightSwitch;
import android.hardware.automotive.vehicle.VehiclePropConfig;
import android.hardware.automotive.vehicle.VehicleProperty;
import android.hardware.automotive.vehicle.VehiclePropertyGroup;
import android.hardware.automotive.vehicle.VehiclePropertyType;
import android.util.Pair;
import android.util.SparseArray;
import com.android.car.CarLog;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A JSON parser class to get configs and values from JSON config files.
*/
public final class FakeVhalConfigParser {
private static final String TAG = CarLog.tagFor(FakeVhalConfigParser.class);
private static final String ENUM_CLASS_DIRECTORY = "android.hardware.automotive.vehicle.";
private static final String JSON_FIELD_NAME_ROOT = "properties";
private static final String JSON_FIELD_NAME_PROPERTY_ID = "property";
private static final String JSON_FIELD_NAME_DEFAULT_VALUE = "defaultValue";
private static final String JSON_FIELD_NAME_AREAS = "areas";
private static final String JSON_FIELD_NAME_CONFIG_ARRAY = "configArray";
private static final String JSON_FIELD_NAME_CONFIG_STRING = "configString";
private static final String JSON_FIELD_NAME_MIN_SAMPLE_RATE = "minSampleRate";
private static final String JSON_FIELD_NAME_MAX_SAMPLE_RATE = "maxSampleRate";
private static final String JSON_FIELD_NAME_AREA_ID = "areaId";
private static final String JSON_FIELD_NAME_INT32_VALUES = "int32Values";
private static final String JSON_FIELD_NAME_INT64_VALUES = "int64Values";
private static final String JSON_FIELD_NAME_FLOAT_VALUES = "floatValues";
private static final String JSON_FIELD_NAME_STRING_VALUE = "stringValue";
private static final String JSON_FIELD_NAME_MIN_INT32_VALUE = "minInt32Value";
private static final String JSON_FIELD_NAME_MAX_INT32_VALUE = "maxInt32Value";
private static final String JSON_FIELD_NAME_MIN_FLOAT_VALUE = "minFloatValue";
private static final String JSON_FIELD_NAME_MAX_FLOAT_VALUE = "maxFloatValue";
private static final String JSON_FIELD_NAME_ACCESS = "access";
private static final String JSON_FIELD_NAME_CHANGE_MODE = "changeMode";
// Following values are defined in PropertyUtils.h file
// (hardware/interfaces/automotive/vehicle/aidl/impl/utils/common/include/PropertyUtils.h).
private static final int DOOR_1_RIGHT = VehicleAreaDoor.ROW_1_RIGHT;
private static final int DOOR_1_LEFT = VehicleAreaDoor.ROW_1_LEFT;
private static final int DOOR_2_RIGHT = VehicleAreaDoor.ROW_2_RIGHT;
private static final int DOOR_2_LEFT = VehicleAreaDoor.ROW_2_LEFT;
private static final int DOOR_REAR = VehicleAreaDoor.REAR;
private static final int VENDOR_EXTENSION_INT_PROPERTY = 0x103 | VehiclePropertyGroup.VENDOR
| VehiclePropertyType.INT32 | VehicleArea.WINDOW;
private static final int VENDOR_EXTENSION_BOOLEAN_PROPERTY = 0x101 | VehiclePropertyGroup.VENDOR
| VehiclePropertyType.BOOLEAN | VehicleArea.DOOR;
private static final int VENDOR_EXTENSION_STRING_PROPERTY = 0x104 | VehiclePropertyGroup.VENDOR
| VehiclePropertyType.STRING | VehicleArea.GLOBAL;
private static final int VENDOR_EXTENSION_FLOAT_PROPERTY = 0x102 | VehiclePropertyGroup.VENDOR
| VehiclePropertyType.FLOAT | VehicleArea.SEAT;
private static final int WINDOW_1_LEFT = VehicleAreaWindow.ROW_1_LEFT;
private static final int WINDOW_1_RIGHT = VehicleAreaWindow.ROW_1_RIGHT;
private static final int WINDOW_2_LEFT = VehicleAreaWindow.ROW_2_LEFT;
private static final int WINDOW_2_RIGHT = VehicleAreaWindow.ROW_2_RIGHT;
private static final int WINDOW_ROOF_TOP_1 = VehicleAreaWindow.ROOF_TOP_1;
private static final int SEAT_1_RIGHT = VehicleAreaSeat.ROW_1_RIGHT;
private static final int SEAT_1_LEFT = VehicleAreaSeat.ROW_1_LEFT;
private static final int SEAT_2_RIGHT = VehicleAreaSeat.ROW_2_RIGHT;
private static final int SEAT_2_LEFT = VehicleAreaSeat.ROW_2_LEFT;
private static final int SEAT_2_CENTER = VehicleAreaSeat.ROW_2_CENTER;
private static final int WHEEL_REAR_RIGHT = VehicleAreaWheel.RIGHT_REAR;
private static final int WHEEL_REAR_LEFT = VehicleAreaWheel.LEFT_REAR;
private static final int WHEEL_FRONT_RIGHT = VehicleAreaWheel.RIGHT_FRONT;
private static final int WHEEL_FRONT_LEFT = VehicleAreaWheel.LEFT_FRONT;
private static final int CHARGE_PORT_FRONT_LEFT = PortLocationType.FRONT_LEFT;
private static final int CHARGE_PORT_REAR_LEFT = PortLocationType.REAR_LEFT;
private static final int FAN_DIRECTION_FLOOR = VehicleHvacFanDirection.FLOOR;
private static final int FAN_DIRECTION_FACE = VehicleHvacFanDirection.FACE;
private static final int FAN_DIRECTION_DEFROST = VehicleHvacFanDirection.DEFROST;
private static final int FUEL_DOOR_REAR_LEFT = PortLocationType.REAR_LEFT;
// TODO(b/241984846) Removed SEAT_2_CENTER from HVAC_LEFT here. May need to change the HVAC_LEFT
// values in other places to make it consistent.
private static final int HVAC_LEFT = SEAT_1_LEFT | SEAT_2_LEFT;
private static final int HVAC_RIGHT = SEAT_1_RIGHT | SEAT_2_RIGHT;
private static final int HVAC_ALL = HVAC_LEFT | HVAC_RIGHT;
private static final int LIGHT_STATE_ON = VehicleLightState.ON;
private static final int LIGHT_SWITCH_AUTO = VehicleLightSwitch.AUTOMATIC;
private static final int MIRROR_DRIVER_LEFT_RIGHT = VehicleAreaMirror.DRIVER_LEFT
| VehicleAreaMirror.DRIVER_RIGHT;
// Following are the test properties whose values are copying from TestPropertyUtils.h file(
// hardware/interfaces/automotive/vehicle/aidl/impl/utils/test/include/TestPropertyUtils.h).
private static final int ECHO_REVERSE_BYTES = 0x2a12 | VehiclePropertyGroup.VENDOR
| VehicleArea.GLOBAL | VehiclePropertyType.BYTES;
private static final int K_MIXED_TYPE_PROPERTY_FOR_TEST = 0x1111 | VehiclePropertyGroup.VENDOR
| VehicleArea.GLOBAL | VehiclePropertyType.MIXED;
private static final int VENDOR_CLUSTER_NAVIGATION_STATE = toVendorProperty(
VehicleProperty.CLUSTER_NAVIGATION_STATE);
private static final int VENDOR_CLUSTER_REQUEST_DISPLAY = toVendorProperty(
VehicleProperty.CLUSTER_REQUEST_DISPLAY);
private static final int VENDOR_CLUSTER_SWITCH_UI = toVendorProperty(
VehicleProperty.CLUSTER_SWITCH_UI);
private static final int VENDOR_CLUSTER_DISPLAY_STATE = toVendorProperty(
VehicleProperty.CLUSTER_DISPLAY_STATE);
private static final int VENDOR_CLUSTER_REPORT_STATE = toVendorProperty(
VehicleProperty.CLUSTER_REPORT_STATE);
private static final int PLACEHOLDER_PROPERTY_INT = 0x2a11 | VehiclePropertyGroup.VENDOR
| VehicleArea.GLOBAL | VehiclePropertyType.INT32;
private static final int PLACEHOLDER_PROPERTY_FLOAT = 0x2a11 | VehiclePropertyGroup.VENDOR
| VehicleArea.GLOBAL | VehiclePropertyType.FLOAT;
private static final int PLACEHOLDER_PROPERTY_BOOLEAN = 0x2a11 | VehiclePropertyGroup.VENDOR
| VehicleArea.GLOBAL | VehiclePropertyType.BOOLEAN;
private static final int PLACEHOLDER_PROPERTY_STRING = 0x2a11 | VehiclePropertyGroup.VENDOR
| VehicleArea.GLOBAL | VehiclePropertyType.STRING;
private static final Map<String, Integer> CONSTANTS_BY_NAME = Map.ofEntries(
Map.entry("DOOR_1_RIGHT", DOOR_1_RIGHT),
Map.entry("DOOR_1_LEFT", DOOR_1_LEFT),
Map.entry("DOOR_2_RIGHT", DOOR_2_RIGHT),
Map.entry("DOOR_2_LEFT", DOOR_2_LEFT),
Map.entry("DOOR_REAR", DOOR_REAR),
Map.entry("HVAC_ALL", HVAC_ALL),
Map.entry("HVAC_LEFT", HVAC_LEFT),
Map.entry("HVAC_RIGHT", HVAC_RIGHT),
Map.entry("VENDOR_EXTENSION_INT_PROPERTY", VENDOR_EXTENSION_INT_PROPERTY),
Map.entry("VENDOR_EXTENSION_BOOLEAN_PROPERTY", VENDOR_EXTENSION_BOOLEAN_PROPERTY),
Map.entry("VENDOR_EXTENSION_STRING_PROPERTY", VENDOR_EXTENSION_STRING_PROPERTY),
Map.entry("VENDOR_EXTENSION_FLOAT_PROPERTY", VENDOR_EXTENSION_FLOAT_PROPERTY),
Map.entry("WINDOW_1_LEFT", WINDOW_1_LEFT),
Map.entry("WINDOW_1_RIGHT", WINDOW_1_RIGHT),
Map.entry("WINDOW_2_LEFT", WINDOW_2_LEFT),
Map.entry("WINDOW_2_RIGHT", WINDOW_2_RIGHT),
Map.entry("WINDOW_ROOF_TOP_1", WINDOW_ROOF_TOP_1),
Map.entry("WINDOW_1_RIGHT_2_LEFT_2_RIGHT", WINDOW_1_RIGHT | WINDOW_2_LEFT
| WINDOW_2_RIGHT),
Map.entry("SEAT_1_LEFT", SEAT_1_LEFT),
Map.entry("SEAT_1_RIGHT", SEAT_1_RIGHT),
Map.entry("SEAT_2_LEFT", SEAT_1_LEFT),
Map.entry("SEAT_2_RIGHT", SEAT_2_RIGHT),
Map.entry("SEAT_2_CENTER", SEAT_2_CENTER),
Map.entry("WHEEL_REAR_RIGHT", WHEEL_REAR_RIGHT),
Map.entry("WHEEL_REAR_LEFT", WHEEL_REAR_LEFT),
Map.entry("WHEEL_FRONT_RIGHT", WHEEL_FRONT_RIGHT),
Map.entry("WHEEL_FRONT_LEFT", WHEEL_FRONT_LEFT),
Map.entry("CHARGE_PORT_FRONT_LEFT", CHARGE_PORT_FRONT_LEFT),
Map.entry("CHARGE_PORT_REAR_LEFT", CHARGE_PORT_REAR_LEFT),
Map.entry("FAN_DIRECTION_FLOOR", FAN_DIRECTION_FLOOR),
Map.entry("FAN_DIRECTION_FACE", FAN_DIRECTION_FACE),
Map.entry("FAN_DIRECTION_DEFROST", FAN_DIRECTION_DEFROST),
Map.entry("FAN_DIRECTION_FACE_FLOOR", FAN_DIRECTION_FACE | FAN_DIRECTION_FLOOR),
Map.entry("FAN_DIRECTION_FACE_DEFROST", FAN_DIRECTION_FACE | FAN_DIRECTION_DEFROST),
Map.entry("FAN_DIRECTION_FLOOR_DEFROST", FAN_DIRECTION_FLOOR | FAN_DIRECTION_DEFROST),
Map.entry("FAN_DIRECTION_FLOOR_DEFROST_FACE", FAN_DIRECTION_FLOOR
| FAN_DIRECTION_DEFROST | FAN_DIRECTION_FACE),
Map.entry("FUEL_DOOR_REAR_LEFT", FUEL_DOOR_REAR_LEFT),
Map.entry("LIGHT_STATE_ON", LIGHT_STATE_ON),
Map.entry("LIGHT_SWITCH_AUTO", LIGHT_SWITCH_AUTO),
Map.entry("MIRROR_DRIVER_LEFT_RIGHT", MIRROR_DRIVER_LEFT_RIGHT),
Map.entry("ECHO_REVERSE_BYTES", ECHO_REVERSE_BYTES),
Map.entry("kMixedTypePropertyForTest", K_MIXED_TYPE_PROPERTY_FOR_TEST),
Map.entry("VENDOR_CLUSTER_NAVIGATION_STATE", VENDOR_CLUSTER_NAVIGATION_STATE),
Map.entry("VENDOR_CLUSTER_REQUEST_DISPLAY", VENDOR_CLUSTER_REQUEST_DISPLAY),
Map.entry("VENDOR_CLUSTER_SWITCH_UI", VENDOR_CLUSTER_SWITCH_UI),
Map.entry("VENDOR_CLUSTER_DISPLAY_STATE", VENDOR_CLUSTER_DISPLAY_STATE),
Map.entry("VENDOR_CLUSTER_REPORT_STATE", VENDOR_CLUSTER_REPORT_STATE),
Map.entry("PLACEHOLDER_PROPERTY_INT", PLACEHOLDER_PROPERTY_INT),
Map.entry("PLACEHOLDER_PROPERTY_FLOAT", PLACEHOLDER_PROPERTY_FLOAT),
Map.entry("PLACEHOLDER_PROPERTY_BOOLEAN", PLACEHOLDER_PROPERTY_BOOLEAN),
Map.entry("PLACEHOLDER_PROPERTY_STRING", PLACEHOLDER_PROPERTY_STRING)
);
/**
* Reads custom config files and parses the JSON root object whose field name is "properties".
*
* @param customConfigFile The custom config JSON file to parse from.
* @return a list of {@link ConfigDeclaration} storing configs and values for each property.
* @throws IOException if unable to read the config file.
* @throws IllegalArgumentException if file is invalid JSON or when a JSONException is caught.
*/
public SparseArray<ConfigDeclaration> parseJsonConfig(File customConfigFile) throws
IOException, IllegalArgumentException {
// Check if config file exists.
if (!isFileValid(customConfigFile)) {
Slogf.w(TAG, "Custom config file: %s is not a valid file.", customConfigFile.getPath());
return new SparseArray<>(/* initialCapacity= */ 0);
}
FileInputStream customConfigFileStream = new FileInputStream(customConfigFile);
if (customConfigFileStream.available() == 0) {
Slogf.w(TAG, "Custom config file: %s is empty.", customConfigFile.getPath());
return new SparseArray<>(/* initialCapacity= */ 0);
}
return parseJsonConfig(customConfigFileStream);
}
/**
* Reads default config file from java resources which is in the type of input stream.
*
* @param configInputStream The {@link InputStream} to parse from.
* @return a list of {@link ConfigDeclaration} storing configs and values for each property.
* @throws IOException if unable to read the config file.
* @throws IllegalArgumentException if file is invalid JSON or when a JSONException is caught.
*/
public SparseArray<ConfigDeclaration> parseJsonConfig(InputStream configInputStream)
throws IOException {
String configString = new String(configInputStream.readAllBytes());
// Parse JSON root object.
JSONObject configJsonObject;
JSONArray configJsonArray;
try {
configJsonObject = new JSONObject(configString);
} catch (JSONException e) {
throw new IllegalArgumentException("This file does not contain a valid JSONObject.", e);
}
try {
configJsonArray = configJsonObject.getJSONArray(JSON_FIELD_NAME_ROOT);
} catch (JSONException e) {
throw new IllegalArgumentException(JSON_FIELD_NAME_ROOT + " field value is not a valid "
+ "JSONArray.", e);
}
SparseArray<ConfigDeclaration> allPropConfigs = new SparseArray<>();
List<String> errors = new ArrayList<>();
// Parse each property.
for (int i = 0; i < configJsonArray.length(); i++) {
JSONObject propertyObject = configJsonArray.optJSONObject(i);
if (propertyObject == null) {
errors.add(JSON_FIELD_NAME_ROOT + " array has an invalid JSON element at index "
+ i);
continue;
}
ConfigDeclaration propConfig = parseEachProperty(propertyObject, errors);
if (propConfig == null) {
errors.add("Unable to parse JSON object: " + propertyObject + " at index " + i);
if (allPropConfigs.size() != 0) {
errors.add("Last successfully parsed property Id: "
+ allPropConfigs.valueAt(allPropConfigs.size() - 1).getConfig().prop);
}
continue;
}
allPropConfigs.put(propConfig.getConfig().prop, propConfig);
}
if (!errors.isEmpty()) {
throw new IllegalArgumentException(String.join("\n", errors));
}
return allPropConfigs;
}
/**
* Parses each property for its configs and values.
*
* @param propertyObject A JSONObject which stores all configs and values of a property.
* @param errors A list to keep all errors.
* @return a {@link ConfigDeclaration} instance, null if failed to parse.
*/
@Nullable
private ConfigDeclaration parseEachProperty(JSONObject propertyObject, List<String> errors) {
int initialErrorCount = errors.size();
List<String> fieldNames = getFieldNames(propertyObject);
if (fieldNames == null) {
errors.add("The JSONObject " + propertyObject + " is empty.");
return null;
}
VehiclePropConfig vehiclePropConfig = new VehiclePropConfig();
vehiclePropConfig.prop = VehicleProperty.INVALID;
boolean isAccessSet = false;
boolean isChangeModeSet = false;
List<VehicleAreaConfig> areaConfigs = new ArrayList<>();
RawPropValues rawPropValues = null;
SparseArray<RawPropValues> defaultValuesByAreaId = new SparseArray<>();
for (int i = 0; i < fieldNames.size(); i++) {
String fieldName = fieldNames.get(i);
switch (fieldName) {
case JSON_FIELD_NAME_PROPERTY_ID:
vehiclePropConfig.prop = parseIntValue(propertyObject, fieldName, errors);
break;
case JSON_FIELD_NAME_CONFIG_STRING:
vehiclePropConfig.configString = parseStringValue(propertyObject, fieldName,
errors);
break;
case JSON_FIELD_NAME_MIN_SAMPLE_RATE:
vehiclePropConfig.minSampleRate = parseFloatValue(propertyObject, fieldName,
errors);
break;
case JSON_FIELD_NAME_MAX_SAMPLE_RATE:
vehiclePropConfig.maxSampleRate = parseFloatValue(propertyObject, fieldName,
errors);
break;
case JSON_FIELD_NAME_ACCESS:
vehiclePropConfig.access = parseIntValue(propertyObject, fieldName, errors);
isAccessSet = true;
break;
case JSON_FIELD_NAME_CHANGE_MODE:
vehiclePropConfig.changeMode = parseIntValue(propertyObject, fieldName, errors);
isChangeModeSet = true;
break;
case JSON_FIELD_NAME_CONFIG_ARRAY:
JSONArray configArray = propertyObject.optJSONArray(fieldName);
if (configArray == null) {
errors.add(fieldName + " doesn't have a mapped JSONArray value.");
continue;
}
vehiclePropConfig.configArray = parseIntArrayValue(configArray, errors);
break;
case JSON_FIELD_NAME_DEFAULT_VALUE:
JSONObject defaultValueObject = propertyObject.optJSONObject(fieldName);
if (defaultValueObject == null) {
Slogf.w(TAG, "%s doesn't have a mapped value.", fieldName);
continue;
}
rawPropValues = parseDefaultValue(defaultValueObject, errors);
break;
case JSON_FIELD_NAME_AREAS:
JSONArray areas = propertyObject.optJSONArray(fieldName);
if (areas == null) {
errors.add(fieldName + " doesn't have a mapped array value.");
continue;
}
for (int j = 0; j < areas.length(); j++) {
JSONObject areaObject = areas.optJSONObject(j);
if (areaObject == null) {
errors.add("Unable to get a JSONObject element for " + fieldName
+ " at index " + j);
continue;
}
Pair<VehicleAreaConfig, RawPropValues> result =
parseAreaConfig(areaObject, errors);
if (result != null) {
areaConfigs.add(result.first);
defaultValuesByAreaId.put(result.first.areaId, result.second);
}
}
vehiclePropConfig.areaConfigs = areaConfigs.toArray(new VehicleAreaConfig[0]);
break;
default:
Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
}
}
if (vehiclePropConfig.prop == VehicleProperty.INVALID) {
errors.add(propertyObject + " doesn't have propId. PropId is required.");
return null;
}
if (errors.size() > initialErrorCount) {
return null;
}
if (!isAccessSet) {
if (AccessForVehicleProperty.values.containsKey(vehiclePropConfig.prop)) {
vehiclePropConfig.access =
AccessForVehicleProperty.values.get(vehiclePropConfig.prop);
} else {
errors.add("Access field is not set for this property: " + propertyObject);
}
}
if (!isChangeModeSet) {
if (ChangeModeForVehicleProperty.values.containsKey(vehiclePropConfig.prop)) {
vehiclePropConfig.changeMode = ChangeModeForVehicleProperty.values
.get(vehiclePropConfig.prop);
} else {
errors.add("ChangeMode field is not set for this property: " + propertyObject);
}
}
return new ConfigDeclaration(vehiclePropConfig, rawPropValues, defaultValuesByAreaId);
}
/**
* Parses area JSON config object.
*
* @param areaObject A JSONObject of field name "areas".
* @param errors The list to store all errors.
* @return a pair of configs and values for one area, null if failed to parse.
*/
@Nullable
private Pair<VehicleAreaConfig, RawPropValues> parseAreaConfig(JSONObject areaObject,
List<String> errors) {
int initialErrorCount = errors.size();
List<String> fieldNames = getFieldNames(areaObject);
if (fieldNames == null) {
errors.add("The JSONObject " + areaObject + " is empty.");
return null;
}
VehicleAreaConfig areaConfig = new VehicleAreaConfig();
RawPropValues defaultValue = null;
boolean hasAreaId = false;
for (int i = 0; i < fieldNames.size(); i++) {
String fieldName = fieldNames.get(i);
switch (fieldName) {
case JSON_FIELD_NAME_AREA_ID:
areaConfig.areaId = parseIntValue(areaObject, fieldName, errors);
hasAreaId = true;
break;
case JSON_FIELD_NAME_MIN_INT32_VALUE:
areaConfig.minInt32Value = parseIntValue(areaObject, fieldName, errors);
break;
case JSON_FIELD_NAME_MAX_INT32_VALUE:
areaConfig.maxInt32Value = parseIntValue(areaObject, fieldName, errors);
break;
case JSON_FIELD_NAME_MIN_FLOAT_VALUE:
areaConfig.minFloatValue = parseFloatValue(areaObject, fieldName, errors);
break;
case JSON_FIELD_NAME_MAX_FLOAT_VALUE:
areaConfig.maxFloatValue = parseFloatValue(areaObject, fieldName, errors);
break;
case JSON_FIELD_NAME_DEFAULT_VALUE:
defaultValue = parseDefaultValue(areaObject.optJSONObject(fieldName), errors);
break;
default:
Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
}
}
if (!hasAreaId) {
errors.add(areaObject + " doesn't have areaId. AreaId is required.");
return null;
}
if (errors.size() > initialErrorCount) {
return null;
}
return Pair.create(areaConfig, defaultValue);
}
/**
* Parses the "defaultValue" field of a property object and area property object.
*
* @param defaultValue The defaultValue JSONObject to be parsed.
* @param errors The list to store all errors.
* @return a {@link RawPropValues} object which stores defaultValue, null if failed to parse.
*/
@Nullable
private RawPropValues parseDefaultValue(JSONObject defaultValue, List<String> errors) {
int initialErrorCount = errors.size();
List<String> fieldNames = getFieldNames(defaultValue);
if (fieldNames == null) {
Slogf.w(TAG, "The JSONObject %s is empty.", defaultValue.toString());
return null;
}
RawPropValues rawPropValues = new RawPropValues();
for (int i = 0; i < fieldNames.size(); i++) {
String fieldName = fieldNames.get(i);
switch (fieldName) {
case JSON_FIELD_NAME_INT32_VALUES: {
JSONArray int32Values = defaultValue.optJSONArray(fieldName);
if (int32Values == null) {
errors.add("Failed to parse the field name: " + fieldName + " for "
+ "defaultValueObject: " + defaultValue);
continue;
}
rawPropValues.int32Values = parseIntArrayValue(int32Values, errors);
break;
}
case JSON_FIELD_NAME_INT64_VALUES: {
JSONArray int64Values = defaultValue.optJSONArray(fieldName);
if (int64Values == null) {
errors.add("Failed to parse the field name: " + fieldName + " for "
+ "defaultValueObject: " + defaultValue);
continue;
}
rawPropValues.int64Values = parseLongArrayValue(int64Values, errors);
break;
}
case JSON_FIELD_NAME_FLOAT_VALUES: {
JSONArray floatValues = defaultValue.optJSONArray(fieldName);
if (floatValues == null) {
errors.add("Failed to parse the field name: " + fieldName + " for "
+ "defaultValueObject: " + defaultValue);
continue;
}
rawPropValues.floatValues = parseFloatArrayValue(floatValues, errors);
break;
}
case JSON_FIELD_NAME_STRING_VALUE: {
rawPropValues.stringValue = parseStringValue(defaultValue, fieldName, errors);
break;
}
default:
Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
}
}
if (errors.size() > initialErrorCount) {
return null;
}
return rawPropValues;
}
/**
* Parses String Json value.
*
* @param parentObject The JSONObject will be parsed.
* @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return a string of parsed value, null if failed to parse.
*/
@Nullable
private String parseStringValue(JSONObject parentObject, String fieldName,
List<String> errors) {
String value = parentObject.optString(fieldName);
if (value.equals("")) {
errors.add(fieldName + " doesn't have a mapped value.");
return null;
}
return value;
}
/**
* Parses int Json value.
*
* @param parentObject The JSONObject will be parsed.
* @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return a value as int, 0 if failed to parse.
*/
private int parseIntValue(JSONObject parentObject, String fieldName, List<String> errors) {
if (isString(parentObject, fieldName)) {
String constantValue;
try {
constantValue = parentObject.getString(fieldName);
return parseConstantValue(constantValue, errors);
} catch (JSONException e) {
errors.add(fieldName + " doesn't have a mapped string value. " + e.getMessage());
return 0;
}
}
Object value = parentObject.opt(fieldName);
if (value != JSONObject.NULL) {
if (value.getClass() == Integer.class) {
return parentObject.optInt(fieldName);
}
errors.add(fieldName + " doesn't have a mapped int value.");
return 0;
}
errors.add(fieldName + " doesn't have a mapped value.");
return 0;
}
/**
* Parses float Json value.
*
* @param parentObject The JSONObject will be parsed.
* @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return the parsed value as float, {@code 0f} if failed to parse.
*/
private float parseFloatValue(JSONObject parentObject, String fieldName, List<String> errors) {
if (isString(parentObject, fieldName)) {
String constantValue;
try {
constantValue = parentObject.getString(fieldName);
} catch (JSONException e) {
errors.add(fieldName + " doesn't have a mapped string value. " + e.getMessage());
return 0f;
}
return (float) parseConstantValue(constantValue, errors);
}
try {
return (float) parentObject.getDouble(fieldName);
} catch (JSONException e) {
errors.add(fieldName + " doesn't have a mapped float value. " + e.getMessage());
return 0f;
}
}
/**
* Parses enum class constant.
*
* @param stringValue The constant string to be parsed.
* @param errors A list to keep all errors.
* @return the int value of an enum constant, 0 if the constant format is invalid.
*/
private int parseConstantValue(String stringValue, List<String> errors) {
String[] propIdStrings = stringValue.split("::");
if (propIdStrings.length != 2 || propIdStrings[0].isEmpty() || propIdStrings[1].isEmpty()) {
errors.add(stringValue + " must in the form of <EnumClassName>::<ConstantName>.");
return 0;
}
String enumClassName = ENUM_CLASS_DIRECTORY + propIdStrings[0];
String constantName = propIdStrings[1];
if (propIdStrings[0].equals("Constants")) {
if (CONSTANTS_BY_NAME.containsKey(constantName)) {
return CONSTANTS_BY_NAME.get(constantName);
}
errors.add(constantName + " is not a valid constant name.");
return 0;
}
Class enumClass;
try {
enumClass = Class.forName(enumClassName);
} catch (ClassNotFoundException e) {
errors.add(enumClassName + " is not a valid class name. " + e.getMessage());
return 0;
}
Field[] fields = enumClass.getDeclaredFields();
for (Field field : fields) {
if (constantName.equals(field.getName())) {
try {
return field.getInt(enumClass);
} catch (Exception e) {
errors.add("Failed to get int value of " + enumClass + "." + constantName
+ " " + e.getMessage());
return 0;
}
}
}
errors.add(enumClass + " doesn't have a constant field with name " + constantName);
return 0;
}
/**
* Parses int values in a {@link JSONArray}.
*
* @param values The JSON array to be parsed.
* @param errors The list to store all errors.
* @return an int array of default values, null if failed to parse.
*/
@Nullable
private int[] parseIntArrayValue(JSONArray values, List<String> errors) {
int initialErrorCount = errors.size();
int[] valueArray = new int[values.length()];
for (int i = 0; i < values.length(); i++) {
if (isString(values, i)) {
String stringValue = values.optString(i);
valueArray[i] = parseConstantValue(stringValue, errors);
} else {
try {
valueArray[i] = values.getInt(i);
} catch (JSONException e) {
errors.add(values + " doesn't have a mapped int value at index " + i + " "
+ e.getMessage());
}
}
}
if (errors.size() > initialErrorCount) {
return null;
}
return valueArray;
}
/**
* Parses long values in a {@link JSONArray}.
*
* @param values The JSON array to be parsed.
* @param errors The list to store all errors.
* @return a long array of default values, null if failed to parse.
*/
@Nullable
private long[] parseLongArrayValue(JSONArray values, List<String> errors) {
int initialErrorCount = errors.size();
long[] valueArray = new long[values.length()];
for (int i = 0; i < values.length(); i++) {
if (isString(values, i)) {
String stringValue = values.optString(i);
valueArray[i] = parseConstantValue(stringValue, errors);
} else {
try {
valueArray[i] = values.getLong(i);
} catch (JSONException e) {
errors.add(values + " doesn't have a mapped long value at index " + i + " "
+ e.getMessage());
}
}
}
if (errors.size() > initialErrorCount) {
return null;
}
return valueArray;
}
/**
* Parses float values in a {@link JSONArray}.
*
* @param values The JSON array to be parsed.
* @param errors The list to store all errors.
* @return a float array of default value, null if failed to parse.
*/
@Nullable
private float[] parseFloatArrayValue(JSONArray values, List<String> errors) {
int initialErrorCount = errors.size();
float[] valueArray = new float[values.length()];
for (int i = 0; i < values.length(); i++) {
if (isString(values, i)) {
String stringValue = values.optString(i);
valueArray[i] = (float) parseConstantValue(stringValue, errors);
} else {
try {
valueArray[i] = (float) values.getDouble(i);
} catch (JSONException e) {
errors.add(values + " doesn't have a mapped float value at index " + i + " "
+ e.getMessage());
}
}
}
if (errors.size() > initialErrorCount) {
return null;
}
return valueArray;
}
/**
* Checks if parentObject contains field and the field is a String.
*
* @param parentObject The JSONObject containing the field.
* @param fieldName The name for the JSON field.
* @return {@code true} if parent object contains this field and the value is string.
*/
private boolean isString(JSONObject parentObject, String fieldName) {
return parentObject.opt(fieldName) != JSONObject.NULL && parentObject.opt(fieldName)
.getClass() == String.class;
}
/**
* Checks if the JSON array contains the index and the element at the index is a String.
*
* @param jsonArray The JSON array to be checked.
* @param index The index of the JSON array element which will be checked.
* @return {@code true} if the JSON array has value at index and the value is string.
*/
private boolean isString(JSONArray jsonArray, int index) {
return jsonArray.opt(index) != JSONObject.NULL && jsonArray.opt(index).getClass()
== String.class;
}
/**
* Gets all field names of a {@link JSONObject}.
*
* @param jsonObject The JSON object to read field names from.
* @return a list of all the field names of an JSONObject, null if the object is empty.
*/
@Nullable
private List<String> getFieldNames(JSONObject jsonObject) {
JSONArray names = jsonObject.names();
if (names == null) {
return null;
}
List<String> fieldNames = new ArrayList<>();
for (int i = 0; i < names.length(); i++) {
String fieldName = names.optString(i);
if (fieldName != null) {
fieldNames.add(fieldName);
}
}
return fieldNames;
}
/**
* Checks if config file exists and has read permission.
*
* @param configFile Either default or custom config JSON file.
* @return A boolean value to determine if config file is valid.
*/
private boolean isFileValid(File configFile) {
return configFile.exists() && configFile.isFile();
}
/**
* Converts system property to vendor property.
*
* @param property The property going to be converted.
* @return an int represents vendor property.
*/
private static int toVendorProperty(int property) {
return (property & VehiclePropertyGroup.MASK) | VehiclePropertyGroup.VENDOR;
}
}