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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR; 20 import static com.android.car.internal.util.ConstantDebugUtils.toName; 21 22 import static java.lang.Integer.toHexString; 23 24 import android.annotation.Nullable; 25 import android.hardware.automotive.vehicle.EnumForVehicleProperty; 26 import android.hardware.automotive.vehicle.UnitsForVehicleProperty; 27 import android.hardware.automotive.vehicle.VehicleArea; 28 import android.hardware.automotive.vehicle.VehicleAreaDoor; 29 import android.hardware.automotive.vehicle.VehicleAreaMirror; 30 import android.hardware.automotive.vehicle.VehicleAreaSeat; 31 import android.hardware.automotive.vehicle.VehicleAreaWheel; 32 import android.hardware.automotive.vehicle.VehicleAreaWindow; 33 import android.hardware.automotive.vehicle.VehicleProperty; 34 import android.hardware.automotive.vehicle.VehiclePropertyAccess; 35 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode; 36 import android.hardware.automotive.vehicle.VehiclePropertyGroup; 37 import android.hardware.automotive.vehicle.VehiclePropertyStatus; 38 import android.hardware.automotive.vehicle.VehiclePropertyType; 39 import android.hardware.automotive.vehicle.VehicleUnit; 40 import android.util.ArrayMap; 41 import android.util.Slog; 42 43 import com.android.car.hal.HalPropValue; 44 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 45 import com.android.car.internal.property.CarPropertyHelper; 46 import com.android.car.internal.property.PropIdAreaId; 47 import com.android.car.internal.util.ConstantDebugUtils; 48 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Collections; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.StringJoiner; 55 import java.util.concurrent.atomic.AtomicReference; 56 57 /** 58 * Utility class for converting {@link VehicleProperty} related information to human-readable names. 59 */ 60 public final class HalPropertyDebugUtils { 61 private static final String TAG = HalPropertyDebugUtils.class.getSimpleName(); 62 private static final int MAX_BYTE_SIZE = 20; 63 private static final AtomicReference<Map<Class<?>, List<Integer>>> sClazzToAreaBitsHolder = 64 new AtomicReference<>(); 65 private static final String NO_VALUE = "NO_VALUE"; 66 67 68 /** 69 * HalPropertyDebugUtils only contains static fields and methods and must never be 70 * instantiated. 71 */ 72 @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR) HalPropertyDebugUtils()73 private HalPropertyDebugUtils() { 74 throw new UnsupportedOperationException("Must never be called"); 75 } 76 77 /** 78 * Gets a user-friendly representation string representation of a {@code propertyId}. 79 */ toPropertyIdString(int propertyId)80 public static String toPropertyIdString(int propertyId) { 81 String hexSuffix = "(0x" + toHexString(propertyId) + ")"; 82 if (isSystemPropertyId(propertyId)) { 83 return toName(VehicleProperty.class, propertyId) + hexSuffix; 84 } else if (CarPropertyHelper.isVendorProperty(propertyId)) { 85 return "VENDOR_PROPERTY" + hexSuffix; 86 } else if (CarPropertyHelper.isBackportedProperty(propertyId)) { 87 return "BACKPORTED_PROPERTY" + hexSuffix; 88 } 89 return "INVALID_PROPERTY_ID" + hexSuffix; 90 } 91 92 /** 93 * Gets the HAL property's ID based on the passed name. 94 */ 95 @Nullable toPropertyId(String propertyName)96 public static Integer toPropertyId(String propertyName) { 97 return ConstantDebugUtils.toValue(VehicleProperty.class, propertyName); 98 } 99 100 /** 101 * Gets a user-friendly string representation of an {@code areaId} for the given 102 * {@code propertyId}. 103 */ toAreaIdString(int propertyId, int areaId)104 public static String toAreaIdString(int propertyId, int areaId) { 105 switch (propertyId & VehicleArea.MASK) { 106 case VehicleArea.GLOBAL -> { 107 if (areaId == 0) { 108 return "GLOBAL(0x0)"; 109 } 110 return "INVALID_GLOBAL_AREA_ID(0x" + toHexString(areaId) + ")"; 111 } 112 case VehicleArea.DOOR -> { 113 return convertAreaIdToDebugString(VehicleAreaDoor.class, areaId); 114 } 115 case VehicleArea.SEAT -> { 116 if (areaId == VehicleAreaSeat.UNKNOWN) { 117 return toName(VehicleAreaSeat.class, areaId); 118 } 119 return convertAreaIdToDebugString(VehicleAreaSeat.class, areaId); 120 } 121 case VehicleArea.MIRROR -> { 122 return convertAreaIdToDebugString(VehicleAreaMirror.class, areaId); 123 } 124 case VehicleArea.WHEEL -> { 125 if (areaId == VehicleAreaWheel.UNKNOWN) { 126 return toName(VehicleAreaWheel.class, areaId); 127 } 128 return convertAreaIdToDebugString(VehicleAreaWheel.class, areaId); 129 } 130 case VehicleArea.WINDOW -> { 131 return convertAreaIdToDebugString(VehicleAreaWindow.class, areaId); 132 } 133 default -> { 134 return "UNKNOWN_AREA_ID(0x" + toHexString(areaId) + ")"; 135 } 136 } 137 } 138 convertAreaIdToDebugString(Class<?> clazz, int areaId)139 private static String convertAreaIdToDebugString(Class<?> clazz, int areaId) { 140 String output = ""; 141 142 Map<Class<?>, List<Integer>> clazzToAreaBits = sClazzToAreaBitsHolder.get(); 143 if (clazzToAreaBits == null || clazzToAreaBits.get(clazz) == null) { 144 clazzToAreaBits = getClazzToAreaBitsMapping(clazzToAreaBits, clazz); 145 sClazzToAreaBitsHolder.set(clazzToAreaBits); 146 } 147 148 int areaBitMask = 0; 149 for (int i = 0; i < clazzToAreaBits.get(clazz).size(); i++) { 150 int areaBit = clazzToAreaBits.get(clazz).get(i).intValue(); 151 if (areaBit == 0) { 152 continue; 153 } 154 areaBitMask |= areaBit; 155 if ((areaId & areaBit) == areaBit) { 156 if (!output.isEmpty()) { 157 output += "|"; 158 } 159 output += toName(clazz, areaBit); 160 } 161 } 162 163 if ((areaId | areaBitMask) != areaBitMask || output.isEmpty()) { 164 output += "INVALID_" + clazz.getSimpleName() + "_AREA_ID"; 165 } 166 167 output += "(0x" + toHexString(areaId) + ")"; 168 return output; 169 } 170 getClazzToAreaBitsMapping( @ullable Map<Class<?>, List<Integer>> clazzToAreaBits, Class<?> clazz)171 private static Map<Class<?>, List<Integer>> getClazzToAreaBitsMapping( 172 @Nullable Map<Class<?>, List<Integer>> clazzToAreaBits, Class<?> clazz) { 173 Map<Class<?>, List<Integer>> outputClazzToAreaBits; 174 if (clazzToAreaBits == null) { 175 outputClazzToAreaBits = new ArrayMap<>(); 176 } else { 177 outputClazzToAreaBits = new ArrayMap<>(clazzToAreaBits.size()); 178 outputClazzToAreaBits.putAll(clazzToAreaBits); 179 } 180 181 List<Integer> areaBits = new ArrayList<>(ConstantDebugUtils.getValues(clazz)); 182 Collections.sort(areaBits, Collections.reverseOrder()); 183 184 outputClazzToAreaBits.put(clazz, areaBits); 185 return outputClazzToAreaBits; 186 } 187 188 /** 189 * Gets a user-friendly representation string representation of the value of a 190 * {@link HalPropValue} instance. 191 */ toValueString(HalPropValue halPropValue)192 public static String toValueString(HalPropValue halPropValue) { 193 int propertyId = halPropValue.getPropId(); 194 int valueType = propertyId & VehiclePropertyType.MASK; 195 String propertyUnits = getUnitsIfSupported(propertyId); 196 StringJoiner stringJoiner = new StringJoiner(", ", "[", "]"); 197 switch (valueType) { 198 case VehiclePropertyType.BOOLEAN -> { 199 if (halPropValue.getInt32ValuesSize() != 1) { 200 return NO_VALUE; 201 } 202 return halPropValue.getInt32Value(0) == 0 ? "FALSE" : "TRUE"; 203 } 204 case VehiclePropertyType.INT32 -> { 205 if (halPropValue.getInt32ValuesSize() != 1) { 206 return NO_VALUE; 207 } 208 return getIntValueName(propertyId, halPropValue.getInt32Value(0), propertyUnits); 209 } 210 case VehiclePropertyType.INT32_VEC -> { 211 for (int i = 0; i < halPropValue.getInt32ValuesSize(); i++) { 212 stringJoiner.add(getIntValueName(propertyId, halPropValue.getInt32Value(i), 213 propertyUnits)); 214 } 215 return stringJoiner.toString(); 216 } 217 case VehiclePropertyType.FLOAT -> { 218 if (halPropValue.getFloatValuesSize() != 1) { 219 return NO_VALUE; 220 } 221 return halPropValue.getFloatValue(0) + propertyUnits; 222 } 223 case VehiclePropertyType.FLOAT_VEC -> { 224 for (int i = 0; i < halPropValue.getFloatValuesSize(); i++) { 225 stringJoiner.add(halPropValue.getFloatValue(i) + propertyUnits); 226 } 227 return stringJoiner.toString(); 228 } 229 case VehiclePropertyType.INT64 -> { 230 if (halPropValue.getInt64ValuesSize() != 1) { 231 return NO_VALUE; 232 } 233 return halPropValue.getInt64Value(0) + propertyUnits; 234 } 235 case VehiclePropertyType.INT64_VEC -> { 236 for (int i = 0; i < halPropValue.getInt64ValuesSize(); i++) { 237 stringJoiner.add(halPropValue.getInt64Value(i) + propertyUnits); 238 } 239 return stringJoiner.toString(); 240 } 241 case VehiclePropertyType.STRING -> { 242 return halPropValue.getStringValue(); 243 } 244 case VehiclePropertyType.BYTES -> { 245 String bytesString = ""; 246 byte[] byteValues = halPropValue.getByteArray(); 247 if (byteValues.length > MAX_BYTE_SIZE) { 248 byte[] bytes = Arrays.copyOf(byteValues, MAX_BYTE_SIZE); 249 bytesString = Arrays.toString(bytes); 250 } else { 251 bytesString = Arrays.toString(byteValues); 252 } 253 return bytesString; 254 } 255 } 256 String bytesString = ""; 257 byte[] byteValues = halPropValue.getByteArray(); 258 if (byteValues.length > MAX_BYTE_SIZE) { 259 byte[] bytes = Arrays.copyOf(byteValues, MAX_BYTE_SIZE); 260 bytesString = Arrays.toString(bytes); 261 } else { 262 bytesString = Arrays.toString(byteValues); 263 } 264 return "floatValues: " + halPropValue.dumpFloatValues() + ", int32Values: " 265 + halPropValue.dumpInt32Values() + ", int64Values: " 266 + halPropValue.dumpInt64Values() + ", bytes: " + bytesString + ", string: " 267 + halPropValue.getStringValue(); 268 } 269 getIntValueName(int propertyId, int value, String propertyUnits)270 private static String getIntValueName(int propertyId, int value, String propertyUnits) { 271 if (EnumForVehicleProperty.values.containsKey(propertyId)) { 272 for (int i = 0; i < EnumForVehicleProperty.values.get(propertyId).size(); i++) { 273 Class<?> enumClazz = EnumForVehicleProperty.values.get(propertyId).get(i); 274 String valueName = ConstantDebugUtils.toName(enumClazz, value); 275 if (valueName != null) { 276 return valueName + "(0x" + toHexString(value) + ")"; 277 } 278 } 279 Slog.w(TAG, 280 "Failed to find enum name for property ID: " + toPropertyIdString(propertyId) 281 + " value: " + value); 282 } 283 return value + propertyUnits; 284 } 285 getUnitsIfSupported(int propertyId)286 private static String getUnitsIfSupported(int propertyId) { 287 if (!UnitsForVehicleProperty.values.containsKey(propertyId)) { 288 return ""; 289 } 290 Integer units = UnitsForVehicleProperty.values.get(propertyId); 291 String unitsString = ConstantDebugUtils.toName(VehicleUnit.class, units); 292 if (unitsString == null) { 293 return ""; 294 } 295 return " " + unitsString; 296 } 297 298 /** 299 * Gets a user-friendly representation string representation of {@link VehicleArea} 300 * constant for the passed {@code propertyId}. 301 */ toAreaTypeString(int propertyId)302 public static String toAreaTypeString(int propertyId) { 303 int areaType = propertyId & VehicleArea.MASK; 304 return toDebugString(VehicleArea.class, areaType); 305 } 306 307 /** 308 * Gets a user-friendly representation string representation of {@link VehiclePropertyGroup} 309 * constant for the passed {@code propertyId}. 310 */ toGroupString(int propertyId)311 public static String toGroupString(int propertyId) { 312 int group = propertyId & VehiclePropertyGroup.MASK; 313 return toDebugString(VehiclePropertyGroup.class, group); 314 } 315 316 /** 317 * Gets a user-friendly representation string representation of {@link VehiclePropertyType} 318 * constant for the passed {@code propertyId}. 319 */ toValueTypeString(int propertyId)320 public static String toValueTypeString(int propertyId) { 321 int valueType = propertyId & VehiclePropertyType.MASK; 322 return toDebugString(VehiclePropertyType.class, valueType); 323 } 324 325 /** 326 * Gets a user-friendly representation string representation of 327 * {@link VehiclePropertyAccess} constant. 328 */ toAccessString(int access)329 public static String toAccessString(int access) { 330 return toDebugString(VehiclePropertyAccess.class, access); 331 } 332 333 /** 334 * Gets a user-friendly representation string representation of 335 * {@link VehiclePropertyChangeMode} constant. 336 */ toChangeModeString(int changeMode)337 public static String toChangeModeString(int changeMode) { 338 return toDebugString(VehiclePropertyChangeMode.class, changeMode); 339 } 340 341 /** 342 * Gets a user-friendly representation string representation of 343 * {@link VehiclePropertyStatus} constant. 344 */ toStatusString(int status)345 public static String toStatusString(int status) { 346 return toDebugString(VehiclePropertyStatus.class, status); 347 } 348 toDebugString(Class<?> clazz, int constantValue)349 private static String toDebugString(Class<?> clazz, int constantValue) { 350 String hexSuffix = "(0x" + toHexString(constantValue) + ")"; 351 if (toName(clazz, constantValue) == null) { 352 String invalidConstantValue = "INVALID_" + clazz.getSimpleName() + hexSuffix; 353 Slog.e(TAG, invalidConstantValue); 354 return invalidConstantValue; 355 } 356 return toName(clazz, constantValue) + hexSuffix; 357 } 358 359 /** 360 * Gets human-readable representation of a {@code PropIdAreaId} structure. 361 * 362 * Note that the property ID is the VHAL property ID, not the CarPropertyManager property ID. 363 */ toHalPropIdAreaIdString(PropIdAreaId propIdAreaId)364 public static String toHalPropIdAreaIdString(PropIdAreaId propIdAreaId) { 365 return "PropIdAreaId{propId=" + toPropertyIdString(propIdAreaId.propId) 366 + ", areaId=" + toAreaIdString(propIdAreaId.propId, propIdAreaId.areaId) + "}"; 367 } 368 369 /** 370 * Gets human-readable representation of a list of {@code PropIdAreaId}. 371 * 372 * Note that the property ID is the VHAL property ID, not the CarPropertyManager property ID. 373 */ toHalPropIdAreaIdsString(Iterable<PropIdAreaId> propIdAreaIds)374 public static String toHalPropIdAreaIdsString(Iterable<PropIdAreaId> propIdAreaIds) { 375 StringBuilder sb = new StringBuilder(); 376 sb.append("["); 377 boolean first = true; 378 for (PropIdAreaId propIdAreaId : propIdAreaIds) { 379 if (first) { 380 first = false; 381 } else { 382 sb.append(", "); 383 } 384 sb.append(toHalPropIdAreaIdString(propIdAreaId)); 385 } 386 sb.append("]"); 387 return sb.toString(); 388 } 389 390 /** 391 * Returns {@code true} if {@code propertyId} is defined in {@link VehicleProperty}. 392 * {@code false} otherwise. 393 */ isSystemPropertyId(int propertyId)394 private static boolean isSystemPropertyId(int propertyId) { 395 return toName(VehicleProperty.class, propertyId) != null; 396 } 397 } 398