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