1 /* 2 * Copyright (C) 2023 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 android.car.cts.utils; 18 19 import android.car.VehicleAreaType; 20 import android.car.VehiclePropertyIds; 21 import android.car.hardware.CarPropertyConfig; 22 import android.util.ArrayMap; 23 import android.util.SparseArray; 24 25 import androidx.annotation.Nullable; 26 27 import com.google.common.collect.ImmutableList; 28 import com.google.common.collect.ImmutableSet; 29 30 import org.json.JSONArray; 31 import org.json.JSONException; 32 import org.json.JSONObject; 33 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.util.ArrayList; 37 import java.util.Iterator; 38 import java.util.List; 39 import java.util.Map; 40 41 /** A parser for CarSvcProps.json which is the config file for car property service. */ 42 public final class CarSvcPropsParser { 43 private static final String CONFIG_RESOURCE_NAME = "CarSvcProps.json"; 44 private static final String JSON_FIELD_NAME_PROPERTIES = "properties"; 45 46 private static final String PERM_TYPE_SINGLE = "single"; 47 private static final String PERM_TYPE_ANYOF = "anyOf"; 48 private static final String PERM_TYPE_ALLOF = "allOf"; 49 private static final String JSON_FIELD_VALUE = "value"; 50 private static final String JSON_FIELD_TYPE = "type"; 51 52 // The CarSvcProps.json file version that we can parse. 53 private static final int CAR_SVC_PROP_JSON_VERSION = 1; 54 55 private final SparseArray<VehiclePropertyIdInfo> mVehiclePropertyIdInfoByPropertyId = 56 new SparseArray<>(); 57 private final List<Integer> mAllPropertyIds = new ArrayList<>(); 58 private final Map<String, List<Integer>> mSystemPropertyIdsByFlag = new ArrayMap<>(); 59 60 public static class VehiclePropertyIdInfo { 61 // This is one of VehiclePropertyIds. 62 public int propertyId; 63 public String propertyName; 64 // The element should be one of VehiclePropertyAccessType 65 public ImmutableSet<Integer> allowedAccessModes; 66 // This is one of VehicleAreaTypeValue. 67 public int areaType; 68 // This is one of VehiclePropertyChangeModeType. 69 public int changeMode; 70 public Class<?> propertyType; 71 // Any one of the read permission in this list is required. 72 public ImmutableSet<String> readPermissions; 73 // Any one of the condition in this list is required. For each condition, all of the 74 // permissions are required. 75 public ImmutableList<ImmutableSet<String>> writePermissions; 76 } 77 parseAllowedAccessModes( JSONArray jsonArray, String propertyName)78 private static ImmutableSet<Integer> parseAllowedAccessModes( 79 JSONArray jsonArray, String propertyName) throws JSONException { 80 ImmutableSet.Builder<Integer> allowedAccessModes = ImmutableSet.builder(); 81 for (int i = 0; i < jsonArray.length(); i++) { 82 String accessModeStr = jsonArray.getString(i); 83 switch (accessModeStr) { 84 case "READ": 85 allowedAccessModes.add(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ); 86 break; 87 case "WRITE": 88 allowedAccessModes.add(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE); 89 break; 90 case "READ_WRITE": 91 allowedAccessModes.add(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE); 92 break; 93 default: 94 throw new IllegalArgumentException( 95 "Invalid access mode: " 96 + accessModeStr 97 + " for property: " 98 + propertyName); 99 } 100 } 101 return allowedAccessModes.build(); 102 } 103 parseChangeMode(String changeModeStr, String propertyName)104 private static int parseChangeMode(String changeModeStr, String propertyName) { 105 switch (changeModeStr) { 106 case "STATIC": 107 return CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC; 108 case "ONCHANGE": 109 return CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE; 110 case "CONTINUOUS": 111 return CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS; 112 default: 113 throw new IllegalArgumentException( 114 "Invalid change mode: " + changeModeStr + " for property: " + propertyName); 115 } 116 } 117 parseAreaType(String areaTypeStr, String propertyName)118 private static int parseAreaType(String areaTypeStr, String propertyName) { 119 switch (areaTypeStr) { 120 case "GLOBAL": 121 return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; 122 case "WINDOW": 123 return VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW; 124 case "SEAT": 125 return VehicleAreaType.VEHICLE_AREA_TYPE_SEAT; 126 case "DOOR": 127 return VehicleAreaType.VEHICLE_AREA_TYPE_DOOR; 128 case "MIRROR": 129 return VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR; 130 case "WHEEL": 131 return VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL; 132 case "VENDOR": 133 return VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR; 134 default: 135 throw new IllegalArgumentException( 136 "Invalid area type: " + areaTypeStr + " for property: " + propertyName); 137 } 138 } 139 parsePropertyType(String propertyTypeStr, String propertyName)140 private static Class<?> parsePropertyType(String propertyTypeStr, String propertyName) { 141 switch (propertyTypeStr) { 142 case "Integer": 143 return Integer.class; 144 case "Integer[]": 145 return Integer[].class; 146 case "Long": 147 return Long.class; 148 case "Long[]": 149 return Long[].class; 150 case "Float": 151 return Float.class; 152 case "Float[]": 153 return Float[].class; 154 case "String": 155 return String.class; 156 case "Boolean": 157 return Boolean.class; 158 case "byte[]": 159 return byte[].class; 160 case "Object[]": 161 return Object[].class; 162 default: 163 throw new IllegalArgumentException( 164 "Invalid property type: " 165 + propertyTypeStr 166 + " for property: " 167 + propertyName); 168 } 169 } 170 getSubPermissions(JSONObject permissionObj, int propertyId)171 private static ImmutableSet<String> getSubPermissions(JSONObject permissionObj, int propertyId) 172 throws JSONException { 173 ImmutableSet.Builder<String> subPermissions = ImmutableSet.builder(); 174 var subFields = permissionObj.getJSONArray(JSON_FIELD_VALUE); 175 for (int i = 0; i < subFields.length(); i++) { 176 var subPermissionObj = subFields.getJSONObject(i); 177 if (!subPermissionObj.getString(JSON_FIELD_TYPE).equals(PERM_TYPE_SINGLE)) { 178 throw new IllegalStateException( 179 "sub permission type must be single for property: " 180 + VehiclePropertyIds.toString(propertyId)); 181 } 182 subPermissions.add(subPermissionObj.getString(JSON_FIELD_VALUE)); 183 } 184 return subPermissions.build(); 185 } 186 parseReadPermission( @ullable JSONObject permissionObj, int propertyId)187 private static ImmutableSet<String> parseReadPermission( 188 @Nullable JSONObject permissionObj, int propertyId) throws JSONException { 189 if (permissionObj == null) { 190 return ImmutableSet.of(); 191 } 192 var type = permissionObj.getString(JSON_FIELD_TYPE); 193 switch (type) { 194 case PERM_TYPE_SINGLE: 195 return ImmutableSet.of(permissionObj.getString(JSON_FIELD_VALUE)); 196 case PERM_TYPE_ANYOF: 197 return getSubPermissions(permissionObj, propertyId); 198 // "allOf" is not supported for read. 199 default: 200 throw new IllegalStateException( 201 "Invalid read permission type: " 202 + type 203 + " for property: " 204 + VehiclePropertyIds.toString(propertyId)); 205 } 206 } 207 parseWritePermission( @ullable JSONObject permissionObj, int propertyId)208 private static ImmutableList<ImmutableSet<String>> parseWritePermission( 209 @Nullable JSONObject permissionObj, int propertyId) throws JSONException { 210 if (permissionObj == null) { 211 return ImmutableList.of(); 212 } 213 var type = permissionObj.getString(JSON_FIELD_TYPE); 214 var permBuilder = ImmutableList.<ImmutableSet<String>>builder(); 215 switch (type) { 216 case PERM_TYPE_SINGLE: 217 return ImmutableList.of(ImmutableSet.of(permissionObj.getString(JSON_FIELD_VALUE))); 218 case PERM_TYPE_ANYOF: 219 for (String subPermission : getSubPermissions(permissionObj, propertyId)) { 220 permBuilder.add(ImmutableSet.of(subPermission)); 221 } 222 return permBuilder.build(); 223 case PERM_TYPE_ALLOF: 224 return permBuilder.add(getSubPermissions(permissionObj, propertyId)).build(); 225 default: 226 throw new IllegalStateException( 227 "Invalid write permission type: " 228 + type 229 + " for property: " 230 + VehiclePropertyIds.toString(propertyId)); 231 } 232 } 233 CarSvcPropsParser()234 public CarSvcPropsParser() { 235 String configString; 236 try (InputStream configFile = 237 this.getClass().getClassLoader().getResourceAsStream(CONFIG_RESOURCE_NAME)) { 238 try { 239 configString = new String(configFile.readAllBytes()); 240 } catch (IOException e) { 241 throw new IllegalStateException( 242 "Cannot read from config file: " + CONFIG_RESOURCE_NAME, e); 243 } 244 } catch (IOException e) { 245 throw new IllegalStateException("Failed to close config resource stream", e); 246 } 247 248 JSONObject configJsonObject; 249 try { 250 configJsonObject = new JSONObject(configString); 251 } catch (JSONException e) { 252 throw new IllegalStateException( 253 "Config file: " 254 + CONFIG_RESOURCE_NAME 255 + " does not contain a valid JSONObject.", 256 e); 257 } 258 259 int version = configJsonObject.optInt("version"); 260 if (version > CAR_SVC_PROP_JSON_VERSION) { 261 throw new IllegalStateException( 262 "Incompatible Car service property config JSON file version, only support: " 263 + CAR_SVC_PROP_JSON_VERSION 264 + ", actually: " 265 + version); 266 } 267 try { 268 JSONObject properties = configJsonObject.getJSONObject(JSON_FIELD_NAME_PROPERTIES); 269 Iterator<String> keysIt = properties.keys(); 270 while (keysIt.hasNext()) { 271 String propertyName = keysIt.next(); 272 JSONObject propertyObj = properties.getJSONObject(propertyName); 273 int propertyId = propertyObj.getInt("propertyId"); 274 // TURN_SIGNAL_STATE is deprecated but still exposed through CarPropertyManager. 275 if (propertyId != VehiclePropertyIds.TURN_SIGNAL_STATE 276 && propertyObj.optBoolean("deprecated")) { 277 continue; 278 } 279 280 String featureFlag = propertyObj.optString("featureFlag"); 281 if (!featureFlag.isEmpty()) { 282 if (mSystemPropertyIdsByFlag.get(featureFlag) == null) { 283 mSystemPropertyIdsByFlag.put(featureFlag, new ArrayList<>()); 284 } 285 mSystemPropertyIdsByFlag.get(featureFlag).add(propertyId); 286 } 287 288 VehiclePropertyIdInfo vehiclePropertyIdInfo = new VehiclePropertyIdInfo(); 289 vehiclePropertyIdInfo.propertyId = propertyId; 290 vehiclePropertyIdInfo.propertyName = propertyName; 291 vehiclePropertyIdInfo.allowedAccessModes = 292 parseAllowedAccessModes( 293 propertyObj.getJSONArray("allowedAccessModes"), propertyName); 294 vehiclePropertyIdInfo.changeMode = 295 parseChangeMode(propertyObj.getString("changeMode"), propertyName); 296 vehiclePropertyIdInfo.propertyType = 297 parsePropertyType(propertyObj.getString("propertyType"), propertyName); 298 vehiclePropertyIdInfo.areaType = 299 parseAreaType(propertyObj.getString("areaType"), propertyName); 300 vehiclePropertyIdInfo.readPermissions = 301 parseReadPermission( 302 propertyObj.optJSONObject("readPermission"), propertyId); 303 vehiclePropertyIdInfo.writePermissions = 304 parseWritePermission( 305 propertyObj.optJSONObject("writePermission"), propertyId); 306 mVehiclePropertyIdInfoByPropertyId.put(propertyId, vehiclePropertyIdInfo); 307 mAllPropertyIds.add(propertyId); 308 } 309 } catch (JSONException e) { 310 throw new IllegalStateException( 311 "Config file: " + CONFIG_RESOURCE_NAME + " has invalid JSON format.", e); 312 } 313 } 314 315 /** Gets the VehiclePropertyId information. */ getVehiclePropertyIdInfo(int propertyId)316 public @Nullable VehiclePropertyIdInfo getVehiclePropertyIdInfo(int propertyId) { 317 return mVehiclePropertyIdInfoByPropertyId.get(propertyId); 318 } 319 320 /** Gets all the defined system property IDs. */ getAllSystemPropertyIds()321 public List<Integer> getAllSystemPropertyIds() { 322 return new ArrayList<>(mAllPropertyIds); 323 } 324 325 /** Gets the defined system property IDs under the given flag. */ getSystemPropertyIdsForFlag(String flag)326 public List<Integer> getSystemPropertyIdsForFlag(String flag) { 327 List<Integer> ids = mSystemPropertyIdsByFlag.get(flag); 328 if (ids == null) { 329 return new ArrayList<Integer>(); 330 } 331 return new ArrayList<Integer>(ids); 332 } 333 } 334