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 com.android.car.hal.property; 18 19 import static android.car.Car.PERMISSION_VENDOR_EXTENSION; 20 21 import static java.util.Objects.requireNonNull; 22 23 import android.annotation.Nullable; 24 import android.car.VehiclePropertyIds; 25 import android.car.builtin.os.TraceHelper; 26 import android.car.builtin.util.Slogf; 27 import android.car.feature.FeatureFlags; 28 import android.car.feature.FeatureFlagsImpl; 29 import android.content.Context; 30 import android.hardware.automotive.vehicle.VehiclePropertyStatus; 31 import android.hardware.automotive.vehicle.VehiclePropertyType; 32 import android.os.Trace; 33 import android.util.ArraySet; 34 import android.util.JsonReader; 35 import android.util.SparseArray; 36 37 import com.android.car.CarLog; 38 import com.android.car.hal.BidirectionalSparseIntArray; 39 import com.android.car.hal.HalPropValue; 40 import com.android.car.hal.property.PropertyPermissionInfo.AllOfPermissions; 41 import com.android.car.hal.property.PropertyPermissionInfo.AnyOfPermissions; 42 import com.android.car.hal.property.PropertyPermissionInfo.PermissionCondition; 43 import com.android.car.hal.property.PropertyPermissionInfo.PropertyPermissions; 44 import com.android.car.hal.property.PropertyPermissionInfo.PropertyPermissionsBuilder; 45 import com.android.car.hal.property.PropertyPermissionInfo.SinglePermission; 46 import com.android.car.internal.property.CarPropertyHelper; 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.io.InputStreamReader; 53 import java.util.ArrayList; 54 import java.util.Collections; 55 import java.util.List; 56 import java.util.Set; 57 58 /** 59 * A singleton class to define which AIDL HAL property IDs are used by PropertyHalService. 60 * This class binds the read and write permissions to the property ID. 61 */ 62 public class PropertyHalServiceConfigs { 63 private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE; 64 private static final Object sLock = new Object(); 65 @GuardedBy("sLock") 66 private static PropertyHalServiceConfigs sPropertyHalServiceConfigs; 67 private static final PermissionCondition SINGLE_PERMISSION_VENDOR_EXTENSION = 68 new SinglePermission(PERMISSION_VENDOR_EXTENSION); 69 /** 70 * Represents one VHAL property that is exposed through 71 * {@link android.car.hardware.property.CarPropertyManager}. 72 * 73 * Note that the property ID is defined in {@link android.car.VehiclePropertyIds} and it might 74 * be different than the property ID used by VHAL, defined in {@link VehicleProperty}. 75 * The latter is represented by {@code halPropId}. 76 * 77 * If the property is an {@code Integer} enum property, its supported enum values are listed 78 * in {@code dataEnums}. If the property is a flag-combination property, its valid bit flag is 79 * listed in {@code validBitFlag}. 80 */ 81 @VisibleForTesting CarSvcPropertyConfig( int propertyId, int halPropId, String propertyName, String description, PropertyPermissions permissions, @Nullable Set<Integer> dataEnums, @Nullable Integer validBitFlag)82 /* package */ record CarSvcPropertyConfig( 83 int propertyId, int halPropId, String propertyName, String description, 84 PropertyPermissions permissions, @Nullable Set<Integer> dataEnums, 85 @Nullable Integer validBitFlag) { 86 public CarSvcPropertyConfig { 87 requireNonNull(propertyName); 88 requireNonNull(description); 89 requireNonNull(permissions); 90 } 91 } 92 93 private static final String LATEST_CONFIG_RESOURCE_NAME = "CarSvcProps.json"; 94 private static final String RELEASED_CONFIG_RESOURCE_NAME = "CarSvcProps-Released.json"; 95 private static final String JSON_FIELD_NAME_PROPERTIES = "properties"; 96 97 private static final String REMOVE_SYSTEM_API_TAGS_FLAG_NAME = 98 "FLAG_VEHICLE_PROPERTY_REMOVE_SYSTEM_API_TAGS"; 99 private static final String FLAG_25Q2_3P_PERMISSIONS = 100 "FLAG_VEHICLE_PROPERTY_25Q2_3P_PERMISSIONS"; 101 private static final String B_FLAG_NAME = "FLAG_ANDROID_B_VEHICLE_PROPERTIES"; 102 103 private final FeatureFlags mFeatureFlags; 104 105 private static final String TAG = CarLog.tagFor(PropertyHalServiceConfigs.class); 106 107 private final SparseArray<Set<Integer>> mHalPropIdToEnumSet = new SparseArray<>(); 108 private final SparseArray<CarSvcPropertyConfig> mHalPropIdToCarSvcConfig; 109 private final SparseArray<CarSvcPropertyConfig> mHalPropIdToReleasedCarSvcConfig; 110 private final BidirectionalSparseIntArray mMgrPropIdToHalPropId; 111 112 private final Object mLock = new Object(); 113 @GuardedBy("mLock") 114 private final SparseArray<PropertyPermissions> mVendorHalPropIdToPermissions = 115 new SparseArray<>(); 116 117 /** 118 * Should only be used in unit tests. Use {@link getInsance} instead. 119 */ 120 @VisibleForTesting PropertyHalServiceConfigs(@ullable FeatureFlags featureFlags)121 public PropertyHalServiceConfigs(@Nullable FeatureFlags featureFlags) { 122 Trace.traceBegin(TRACE_TAG, "initialize PropertyHalServiceConfigs"); 123 if (featureFlags == null) { 124 mFeatureFlags = new FeatureFlagsImpl(); 125 } else { 126 mFeatureFlags = featureFlags; 127 } 128 129 // Read config from previous release. 130 mHalPropIdToReleasedCarSvcConfig = readJsonConfig(RELEASED_CONFIG_RESOURCE_NAME, null); 131 // Read latest generated config. 132 mHalPropIdToCarSvcConfig = readJsonConfig( 133 LATEST_CONFIG_RESOURCE_NAME, mHalPropIdToReleasedCarSvcConfig); 134 135 List<Integer> halPropIdMgrIds = new ArrayList<>(); 136 for (int i = 0; i < mHalPropIdToCarSvcConfig.size(); i++) { 137 CarSvcPropertyConfig config = mHalPropIdToCarSvcConfig.valueAt(i); 138 if (config.halPropId() != config.propertyId()) { 139 halPropIdMgrIds.add(config.propertyId()); 140 halPropIdMgrIds.add(config.halPropId()); 141 } 142 } 143 int[] halPropIdMgrIdArray = new int[halPropIdMgrIds.size()]; 144 for (int i = 0; i < halPropIdMgrIds.size(); i++) { 145 halPropIdMgrIdArray[i] = halPropIdMgrIds.get(i); 146 } 147 mMgrPropIdToHalPropId = BidirectionalSparseIntArray.create(halPropIdMgrIdArray); 148 Trace.traceEnd(TRACE_TAG); 149 } 150 151 /** 152 * Common helper function for reading a car svc json config in an input stream, and the parsing 153 * the json for the car property config information. 154 * 155 * @throws IllegalStateException if failed to open/close resource input stream. 156 */ readJsonConfig(String resourceName, SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)157 private SparseArray<CarSvcPropertyConfig> readJsonConfig(String resourceName, 158 SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig) { 159 try (InputStream defaultConfigInputStream = 160 this.getClass().getClassLoader().getResourceAsStream(resourceName)) { 161 return parseJsonConfig(defaultConfigInputStream, 162 "defaultResource", halPropIdToReleasedCarSvcConfig); 163 } catch (IOException e) { 164 String errorMsg = "Failed to open/close resource input stream for: " + resourceName; 165 Slogf.e(TAG, errorMsg, e); 166 throw new IllegalStateException(errorMsg, e); 167 } 168 } 169 170 /** 171 * Gets the hard-coded HAL property ID to enum value set. For unit test only. 172 * 173 * TODO(b/293354967): Remove this once we migrate to runtime config. 174 */ 175 @VisibleForTesting getHalPropIdToEnumSet()176 /* package */ SparseArray<Set<Integer>> getHalPropIdToEnumSet() { 177 return mHalPropIdToEnumSet; 178 } 179 180 /** 181 * Gets a list of all system VHAL property IDs. For unit test only. 182 */ 183 @VisibleForTesting getAllSystemHalPropIds()184 /* package */ List<Integer> getAllSystemHalPropIds() { 185 List<Integer> halPropIds = new ArrayList<>(); 186 for (int i = 0; i < mHalPropIdToCarSvcConfig.size(); i++) { 187 halPropIds.add(mHalPropIdToCarSvcConfig.keyAt(i)); 188 } 189 return halPropIds; 190 } 191 192 /** 193 * Gets the singleton instance for {@link PropertyHalServiceConfigs}. 194 */ getInstance()195 public static PropertyHalServiceConfigs getInstance() { 196 synchronized (sLock) { 197 if (sPropertyHalServiceConfigs == null) { 198 sPropertyHalServiceConfigs = new PropertyHalServiceConfigs( 199 /* featureFlags= */ null); 200 } 201 return sPropertyHalServiceConfigs; 202 } 203 } 204 205 /** 206 * Create a new instance for {@link PropertyHalServiceConfigs}. 207 */ 208 @VisibleForTesting newConfigs()209 public static PropertyHalServiceConfigs newConfigs() { 210 return new PropertyHalServiceConfigs(/* featureFlags= */ null); 211 } 212 213 /** 214 * Returns all possible supported enum values for the {@code halPropId}. If property does not 215 * support an enum, then it returns {@code null}. 216 */ 217 @Nullable getAllPossibleSupportedEnumValues(int halPropId)218 public Set<Integer> getAllPossibleSupportedEnumValues(int halPropId) { 219 if (!mHalPropIdToCarSvcConfig.contains(halPropId)) { 220 return null; 221 } 222 Set<Integer> dataEnums = mHalPropIdToCarSvcConfig.get(halPropId).dataEnums; 223 return dataEnums != null ? Collections.unmodifiableSet(dataEnums) : null; 224 } 225 226 /** 227 * Checks property value's format for all properties. Checks property value range if property 228 * has @data_enum flag in types.hal. 229 * @return true if property value's payload is valid. 230 */ checkPayload(HalPropValue propValue)231 public boolean checkPayload(HalPropValue propValue) { 232 int propId = propValue.getPropId(); 233 // Mixed property uses config array to indicate the data format. Checked it when convert it 234 // to CarPropertyValue. 235 if ((propId & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED) { 236 return true; 237 } 238 if (propValue.getStatus() != VehiclePropertyStatus.AVAILABLE) { 239 return true; 240 } 241 if (!checkFormatForAllProperties(propValue)) { 242 Slogf.e(TAG, "Property value: " + propValue + " has an invalid data format"); 243 return false; 244 } 245 CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get(propId); 246 if (carSvcPropertyConfig == null) { 247 // This is not a system property. 248 return true; 249 } 250 if (carSvcPropertyConfig.dataEnums() != null) { 251 return checkDataEnum(propValue, carSvcPropertyConfig.dataEnums()); 252 } 253 if (carSvcPropertyConfig.validBitFlag() != null) { 254 return checkValidBitFlag(propValue, carSvcPropertyConfig.validBitFlag()); 255 } 256 return true; 257 } 258 259 /** 260 * Gets readPermission using a HAL-level propertyId. 261 * 262 * @param halPropId HAL-level propertyId 263 * @return PermissionCondition object that represents halPropId's readPermission 264 */ 265 @Nullable getReadPermission(int halPropId)266 public PermissionCondition getReadPermission(int halPropId) { 267 if (CarPropertyHelper.isVendorOrBackportedProperty(halPropId)) { 268 return getVendorReadPermission(halPropId); 269 } 270 CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get( 271 halPropId); 272 if (carSvcPropertyConfig == null) { 273 Slogf.w(TAG, "halPropId: " + halPropIdToName(halPropId) + " is not supported," 274 + " no read permission"); 275 return null; 276 } 277 return carSvcPropertyConfig.permissions().readPermission(); 278 } 279 280 @Nullable getVendorReadPermission(int halPropId)281 private PermissionCondition getVendorReadPermission(int halPropId) { 282 String halPropIdName = halPropIdToName(halPropId); 283 synchronized (mLock) { 284 PropertyPermissions propertyPermissions = mVendorHalPropIdToPermissions.get(halPropId); 285 if (propertyPermissions == null) { 286 Slogf.v(TAG, "no custom vendor read permission for: " + halPropIdName 287 + ", default to PERMISSION_VENDOR_EXTENSION"); 288 return SINGLE_PERMISSION_VENDOR_EXTENSION; 289 } 290 PermissionCondition readPermission = propertyPermissions.readPermission(); 291 if (readPermission == null) { 292 Slogf.v(TAG, "vendor propId is not available for reading: " + halPropIdName); 293 } 294 return readPermission; 295 } 296 } 297 298 /** 299 * Gets writePermission using a HAL-level propertyId. 300 * 301 * @param halPropId HAL-level propertyId 302 * @return PermissionCondition object that represents halPropId's writePermission 303 */ 304 @Nullable getWritePermission(int halPropId)305 public PermissionCondition getWritePermission(int halPropId) { 306 if (CarPropertyHelper.isVendorOrBackportedProperty(halPropId)) { 307 return getVendorWritePermission(halPropId); 308 } 309 CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get( 310 halPropId); 311 if (carSvcPropertyConfig == null) { 312 Slogf.w(TAG, "halPropId: " + halPropIdToName(halPropId) + " is not supported," 313 + " no write permission"); 314 return null; 315 } 316 return carSvcPropertyConfig.permissions().writePermission(); 317 } 318 319 @Nullable getVendorWritePermission(int halPropId)320 private PermissionCondition getVendorWritePermission(int halPropId) { 321 String halPropIdName = halPropIdToName(halPropId); 322 synchronized (mLock) { 323 PropertyPermissions propertyPermissions = mVendorHalPropIdToPermissions.get(halPropId); 324 if (propertyPermissions == null) { 325 Slogf.v(TAG, "no custom vendor write permission for: " + halPropIdName 326 + ", default to PERMISSION_VENDOR_EXTENSION"); 327 return SINGLE_PERMISSION_VENDOR_EXTENSION; 328 } 329 PermissionCondition writePermission = propertyPermissions.writePermission(); 330 if (writePermission == null) { 331 Slogf.v(TAG, "vendor propId is not available for writing: " + halPropIdName); 332 } 333 return writePermission; 334 } 335 } 336 337 /** 338 * Checks if readPermission is granted for a HAL-level propertyId in a given context. 339 * 340 * @param halPropId HAL-level propertyId 341 * @param context Context to check 342 * @return readPermission is granted or not. 343 */ isReadable(Context context, int halPropId)344 public boolean isReadable(Context context, int halPropId) { 345 PermissionCondition readPermission = getReadPermission(halPropId); 346 if (readPermission == null) { 347 Slogf.v(TAG, "propId is not readable: " + halPropIdToName(halPropId)); 348 return false; 349 } 350 return readPermission.isMet(context); 351 } 352 353 /** 354 * Checks if writePermission is granted for a HAL-level propertyId in a given context. 355 * 356 * @param halPropId HAL-level propertyId 357 * @param context Context to check 358 * @return writePermission is granted or not. 359 */ isWritable(Context context, int halPropId)360 public boolean isWritable(Context context, int halPropId) { 361 PermissionCondition writePermission = getWritePermission(halPropId); 362 if (writePermission == null) { 363 Slogf.v(TAG, "propId is not writable: " + halPropIdToName(halPropId)); 364 return false; 365 } 366 return writePermission.isMet(context); 367 } 368 369 /** 370 * Checks if property ID is in the list of known IDs that PropertyHalService is interested it. 371 */ isSupportedProperty(int propId)372 public boolean isSupportedProperty(int propId) { 373 return mHalPropIdToCarSvcConfig.get(propId) != null 374 || CarPropertyHelper.isVendorOrBackportedProperty(propId); 375 } 376 377 /** 378 * Overrides the permission map for vendor properties 379 * 380 * @param configArray the configArray for 381 * {@link VehicleProperty#SUPPORT_CUSTOMIZE_VENDOR_PERMISSION} 382 */ customizeVendorPermission(int[] configArray)383 public void customizeVendorPermission(int[] configArray) { 384 if (configArray == null || configArray.length % 3 != 0) { 385 throw new IllegalArgumentException( 386 "ConfigArray for SUPPORT_CUSTOMIZE_VENDOR_PERMISSION is wrong"); 387 } 388 int index = 0; 389 while (index < configArray.length) { 390 int propId = configArray[index++]; 391 if (!CarPropertyHelper.isVendorOrBackportedProperty(propId)) { 392 throw new IllegalArgumentException("Property Id: " + propId 393 + " is not in vendor range"); 394 } 395 int readPermission = configArray[index++]; 396 int writePermission = configArray[index++]; 397 String readPermissionStr = PropertyPermissionInfo.toPermissionString( 398 readPermission, propId); 399 String writePermissionStr = PropertyPermissionInfo.toPermissionString( 400 writePermission, propId); 401 synchronized (mLock) { 402 if (mVendorHalPropIdToPermissions.get(propId) != null) { 403 Slogf.e(TAG, "propId is a vendor property that already exists in " 404 + "mVendorHalPropIdToPermissions and thus cannot have its " 405 + "permissions overwritten: " + halPropIdToName(propId)); 406 continue; 407 } 408 409 PropertyPermissionsBuilder propertyPermissionBuilder = 410 new PropertyPermissionsBuilder(); 411 if (readPermissionStr != null) { 412 propertyPermissionBuilder.setReadPermission( 413 new SinglePermission(readPermissionStr)); 414 } 415 if (writePermissionStr != null) { 416 propertyPermissionBuilder.setWritePermission( 417 new SinglePermission(writePermissionStr)); 418 } 419 420 mVendorHalPropIdToPermissions.put(propId, propertyPermissionBuilder.build()); 421 } 422 } 423 } 424 425 /** 426 * Converts manager property ID to Vehicle HAL property ID. 427 */ managerToHalPropId(int mgrPropId)428 public int managerToHalPropId(int mgrPropId) { 429 return mMgrPropIdToHalPropId.getValue(mgrPropId, mgrPropId); 430 } 431 432 /** 433 * Converts Vehicle HAL property ID to manager property ID. 434 */ halToManagerPropId(int halPropId)435 public int halToManagerPropId(int halPropId) { 436 return mMgrPropIdToHalPropId.getKey(halPropId, halPropId); 437 } 438 439 /** 440 * Print out the name for a VHAL property Id. 441 * 442 * For debugging only. 443 */ halPropIdToName(int halPropId)444 public String halPropIdToName(int halPropId) { 445 return VehiclePropertyIds.toString(halToManagerPropId(halPropId)); 446 } 447 checkValidBitFlag(HalPropValue propValue, int flagCombination)448 private static boolean checkValidBitFlag(HalPropValue propValue, int flagCombination) { 449 for (int i = 0; i < propValue.getInt32ValuesSize(); i++) { 450 int value = propValue.getInt32Value(i); 451 if ((value & flagCombination) != value) { 452 return false; 453 } 454 } 455 return true; 456 } 457 458 /** 459 * Parses the latest car service JSON config file with an already parsed released-version 460 * of the car service JSON config file. Only exposed for testing. 461 */ 462 @VisibleForTesting parseJsonConfig( InputStream configFile, String path, @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)463 /* package */ SparseArray<CarSvcPropertyConfig> parseJsonConfig( 464 InputStream configFile, 465 String path, 466 @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig) { 467 try { 468 SparseArray<CarSvcPropertyConfig> configs = new SparseArray<>(); 469 try (var reader = new JsonReader(new InputStreamReader(configFile, "UTF-8"))) { 470 reader.setLenient(true); 471 parseObjectEntry(reader, () -> { 472 if (!reader.nextName().equals(JSON_FIELD_NAME_PROPERTIES)) { 473 reader.skipValue(); 474 return; 475 } 476 parseObjectEntry(reader, () -> { 477 String propertyName = reader.nextName(); 478 CarSvcPropertyConfig config; 479 try { 480 config = readPropertyObject( 481 propertyName, reader, halPropIdToReleasedCarSvcConfig); 482 } catch (IllegalArgumentException e) { 483 throw new IllegalArgumentException("Invalid json config for property: " 484 + propertyName + ", error: " + e); 485 } 486 if (config == null) { 487 return; 488 } 489 configs.put(config.halPropId(), config); 490 }); 491 }); 492 } 493 return configs; 494 } catch (IllegalStateException | IOException e) { 495 throw new IllegalArgumentException("Config file: " + path 496 + " does not contain a valid JSON object.", e); 497 } 498 } 499 readPropertyObject( String propertyName, JsonReader reader, @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)500 private @Nullable CarSvcPropertyConfig readPropertyObject( 501 String propertyName, JsonReader reader, 502 @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig) 503 throws IOException { 504 String featureFlag = null; 505 boolean deprecated = false; 506 int propertyId = 0; 507 int vhalPropertyId = 0; 508 String description = null; 509 ArraySet<Integer> dataEnums = new ArraySet<Integer>(); 510 List<Integer> dataFlags = new ArrayList<>(); 511 PermissionCondition readPermission = null; 512 PermissionCondition writePermission = null; 513 // Starts parsing each field. 514 reader.beginObject(); 515 while (reader.hasNext()) { 516 String name = reader.nextName(); 517 switch (name) { 518 case "featureFlag": 519 featureFlag = reader.nextString(); 520 break; 521 case "deprecated": 522 deprecated = reader.nextBoolean(); 523 break; 524 case "propertyId": 525 propertyId = reader.nextInt(); 526 break; 527 case "vhalPropertyId": 528 vhalPropertyId = reader.nextInt(); 529 break; 530 case "description": 531 description = reader.nextString(); 532 break; 533 case "dataEnums": 534 reader.beginArray(); 535 while (reader.hasNext()) { 536 dataEnums.add(reader.nextInt()); 537 } 538 reader.endArray(); 539 break; 540 case "dataFlags": 541 reader.beginArray(); 542 while (reader.hasNext()) { 543 dataFlags.add(reader.nextInt()); 544 } 545 reader.endArray(); 546 break; 547 case "readPermission": 548 try { 549 readPermission = parsePermissionCondition(reader); 550 } catch (IllegalArgumentException e) { 551 throw new IllegalArgumentException( 552 "Failed to parse read permissions for property: " + propertyName 553 + ", error: " + e); 554 } 555 break; 556 case "writePermission": 557 try { 558 writePermission = parsePermissionCondition(reader); 559 } catch (IllegalArgumentException e) { 560 throw new IllegalArgumentException( 561 "Failed to parse write permissions for property: " + propertyName 562 + ", error: " + e); 563 } 564 break; 565 default: 566 reader.skipValue(); 567 } 568 } 569 reader.endObject(); 570 571 // Finished parsing each field, now check whether the required fields are present and 572 // assign them to config. 573 if (deprecated) { 574 return null; 575 } 576 if (featureFlag != null) { 577 switch (featureFlag) { 578 case REMOVE_SYSTEM_API_TAGS_FLAG_NAME: 579 // do nothing as no behavior change 580 break; 581 case FLAG_25Q2_3P_PERMISSIONS: 582 // Parsing the older released config. Skip the flag specific logic and continue. 583 if (halPropIdToReleasedCarSvcConfig == null) { 584 break; 585 } 586 // If flag is disabled, use vehicle property entry from old json file. 587 if (!mFeatureFlags.vehicleProperty25q23pPermissions()) { 588 return getReleasedCarSvcPropertyConfig(propertyId, featureFlag, 589 propertyName, halPropIdToReleasedCarSvcConfig); 590 } 591 break; 592 case B_FLAG_NAME: 593 // Parsing the older released config. Skip the flag specific logic and continue. 594 if (halPropIdToReleasedCarSvcConfig == null) { 595 break; 596 } 597 if (!mFeatureFlags.androidBVehicleProperties() 598 && propertyId == VehiclePropertyIds.PERF_ODOMETER) { 599 return getReleasedCarSvcPropertyConfig( 600 propertyId, 601 featureFlag, 602 propertyName, 603 halPropIdToReleasedCarSvcConfig); 604 } 605 if (!mFeatureFlags.androidBVehicleProperties()) { 606 Slogf.w(TAG, "The required feature flag for property: %s is not enabled, " 607 + "so its config is ignored", propertyName); 608 return null; 609 } 610 break; 611 default: 612 throw new IllegalArgumentException("Unknown feature flag: " 613 + featureFlag + " for property: " + propertyName); 614 } 615 } 616 if (description == null) { 617 throw new IllegalArgumentException("Missing required description field for property: " 618 + propertyName); 619 } 620 if (propertyId == 0) { 621 throw new IllegalArgumentException("Missing required propertyId field for property: " 622 + propertyName); 623 } 624 int halPropId; 625 if (vhalPropertyId != 0) { 626 halPropId = vhalPropertyId; 627 } else { 628 halPropId = propertyId; 629 } 630 if (dataEnums.isEmpty()) { 631 dataEnums = null; 632 } 633 Integer validBitFlag = null; 634 if (!dataFlags.isEmpty()) { 635 validBitFlag = generateAllCombination(dataFlags); 636 } 637 if (readPermission == null && writePermission == null) { 638 throw new IllegalArgumentException( 639 "No read or write permission specified for: " + propertyName); 640 } 641 var builder = new PropertyPermissionsBuilder(); 642 if (readPermission != null) { 643 builder.setReadPermission(readPermission); 644 } 645 if (writePermission != null) { 646 builder.setWritePermission(writePermission); 647 } 648 PropertyPermissions permissions = builder.build(); 649 return new CarSvcPropertyConfig(propertyId, halPropId, propertyName, description, 650 permissions, dataEnums, validBitFlag); 651 } 652 getReleasedCarSvcPropertyConfig( int propertyId, String featureFlag, String propertyName, @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)653 private CarSvcPropertyConfig getReleasedCarSvcPropertyConfig( 654 int propertyId, 655 String featureFlag, 656 String propertyName, 657 @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig) { 658 for (int i = 0; i < halPropIdToReleasedCarSvcConfig.size(); i++) { 659 CarSvcPropertyConfig config = 660 halPropIdToReleasedCarSvcConfig.valueAt(i); 661 if (config.propertyId() == propertyId) { 662 return config; 663 } 664 } 665 // If no matching property id, then something is wrong. Every 666 // property changed in this feature flag should have been in the 667 // previous release. 668 throw new IllegalArgumentException("Unknown flag config: " 669 + featureFlag + " for property: " + propertyName); 670 } 671 672 private interface RunanbleWithException { run()673 void run() throws IOException; 674 } 675 parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)676 private static void parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry) 677 throws IOException { 678 reader.beginObject(); 679 while (reader.hasNext()) { 680 forEachEntry.run(); 681 } 682 reader.endObject(); 683 } 684 parsePermissionCondition(JsonReader reader)685 private static PermissionCondition parsePermissionCondition(JsonReader reader) 686 throws IOException { 687 // we only have one type, use a list to be effective-final. 688 List<String> types = new ArrayList<>(); 689 List<PermissionCondition> permissions = new ArrayList<>(); 690 parseObjectEntry(reader, () -> { 691 String name = reader.nextName(); 692 switch (name) { 693 case "type": 694 types.add(reader.nextString()); 695 break; 696 case "value": 697 try { 698 permissions.add(new SinglePermission(reader.nextString())); 699 } catch (IllegalStateException e) { 700 // The value field is not a string, then it must be an array. 701 reader.beginArray(); 702 while (reader.hasNext()) { 703 permissions.add(parsePermissionCondition(reader)); 704 } 705 reader.endArray(); 706 } 707 break; 708 default: 709 reader.skipValue(); 710 } 711 }); 712 if (types.size() == 0) { 713 throw new IllegalArgumentException("Missing type field for permission"); 714 } 715 String type = types.get(0); 716 if (permissions.size() < 1) { 717 throw new IllegalArgumentException("Missing valid value field for permission"); 718 } 719 if (type.equals("single")) { 720 return permissions.get(0); 721 } 722 if (type.equals("anyOf")) { 723 return new AnyOfPermissions(permissions.toArray(new PermissionCondition[0])); 724 } 725 if (type.equals("allOf")) { 726 return new AllOfPermissions(permissions.toArray(new PermissionCondition[0])); 727 } 728 throw new IllegalArgumentException("Invalid permission type: " + type); 729 } 730 checkFormatForAllProperties(HalPropValue propValue)731 private static boolean checkFormatForAllProperties(HalPropValue propValue) { 732 int propId = propValue.getPropId(); 733 int vehiclePropertyType = propId & VehiclePropertyType.MASK; 734 735 // Records sum size of int32values, floatValue, int64Values, bytes, String 736 int sizeOfAllValue = propValue.getInt32ValuesSize() + propValue.getFloatValuesSize() 737 + propValue.getInt64ValuesSize() + propValue.getByteValuesSize() 738 + propValue.getStringValue().length(); 739 if (sizeOfAllValue == 0 740 && vehiclePropertyType != VehiclePropertyType.FLOAT_VEC 741 && vehiclePropertyType != VehiclePropertyType.INT64_VEC 742 && vehiclePropertyType != VehiclePropertyType.INT32_VEC) { 743 Slogf.e(TAG, "Property value is empty: " + propValue); 744 return false; 745 } 746 747 switch (vehiclePropertyType) { 748 case VehiclePropertyType.BOOLEAN: 749 case VehiclePropertyType.INT32: 750 return sizeOfAllValue == 1 && propValue.getInt32ValuesSize() == 1; 751 case VehiclePropertyType.FLOAT: 752 return sizeOfAllValue == 1 && propValue.getFloatValuesSize() == 1; 753 case VehiclePropertyType.INT64: 754 return sizeOfAllValue == 1 && propValue.getInt64ValuesSize() == 1; 755 case VehiclePropertyType.FLOAT_VEC: 756 return sizeOfAllValue == propValue.getFloatValuesSize(); 757 case VehiclePropertyType.INT64_VEC: 758 return sizeOfAllValue == propValue.getInt64ValuesSize(); 759 case VehiclePropertyType.INT32_VEC: 760 return sizeOfAllValue == propValue.getInt32ValuesSize(); 761 case VehiclePropertyType.BYTES: 762 return sizeOfAllValue == propValue.getByteValuesSize(); 763 case VehiclePropertyType.STRING: 764 return sizeOfAllValue == propValue.getStringValue().length(); 765 default: 766 throw new IllegalArgumentException("Unexpected property type for propId: " 767 + Integer.toHexString(propId)); 768 } 769 } 770 checkDataEnum(HalPropValue propValue, Set<Integer> enums)771 private static boolean checkDataEnum(HalPropValue propValue, Set<Integer> enums) { 772 for (int i = 0; i < propValue.getInt32ValuesSize(); i++) { 773 if (!enums.contains(propValue.getInt32Value(i))) { 774 return false; 775 } 776 } 777 return true; 778 } 779 generateAllCombination(List<Integer> bitFlags)780 private static int generateAllCombination(List<Integer> bitFlags) { 781 int combination = bitFlags.get(0); 782 for (int i = 1; i < bitFlags.size(); i++) { 783 combination |= bitFlags.get(i); 784 } 785 return combination; 786 } 787 } 788