1 /* 2 * Copyright (C) 2018 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 package com.android.car.hal; 17 18 import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue; 19 import static com.android.car.hal.CarPropertyUtils.toMixedCarPropertyValue; 20 import static com.android.car.hal.CarPropertyUtils.toMixedVehiclePropValue; 21 import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue; 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 24 import static java.lang.Integer.toHexString; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.car.VehiclePropertyIds; 29 import android.car.hardware.CarPropertyConfig; 30 import android.car.hardware.CarPropertyValue; 31 import android.car.hardware.property.CarPropertyEvent; 32 import android.car.hardware.property.CarPropertyManager; 33 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 34 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 35 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 36 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType; 37 import android.os.Build; 38 import android.os.ServiceSpecificException; 39 import android.util.Pair; 40 import android.util.Slog; 41 import android.util.SparseArray; 42 43 import com.android.car.CarLog; 44 import com.android.car.CarServiceUtils; 45 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 46 import com.android.internal.annotations.GuardedBy; 47 48 import java.io.PrintWriter; 49 import java.util.Collection; 50 import java.util.HashSet; 51 import java.util.LinkedList; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Set; 55 56 /** 57 * Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty. 58 * Services that communicate by passing vehicle properties back and forth via ICarProperty should 59 * extend this class. 60 */ 61 public class PropertyHalService extends HalServiceBase { 62 private final boolean mDbg = true; 63 private final LinkedList<CarPropertyEvent> mEventsToDispatch = new LinkedList<>(); 64 // Use SparseArray to save memory. 65 @GuardedBy("mLock") 66 private final SparseArray<CarPropertyConfig<?>> mMgrPropIdToCarPropConfig = new SparseArray<>(); 67 @GuardedBy("mLock") 68 private final SparseArray<VehiclePropConfig> mHalPropIdToVehiclePropConfig = 69 new SparseArray<>(); 70 @GuardedBy("mLock") 71 private final SparseArray<Pair<String, String>> mMgrPropIdToPermissions = new SparseArray<>(); 72 // Only contains propId if the property Id is different in HAL and manager 73 private static final Map<Integer, Integer> PROPERTY_ID_HAL_TO_MANAGER = Map.of( 74 VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS, 75 VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS); 76 // Only contains propId if the property Id is different in HAL and manager 77 private static final Map<Integer, Integer> PROPERTY_ID_MANAGER_TO_HAL = Map.of( 78 VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS, 79 VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS); 80 private static final String TAG = CarLog.tagFor(PropertyHalService.class); 81 private final VehicleHal mVehicleHal; 82 private final PropertyHalServiceIds mPropIds; 83 84 @GuardedBy("mLock") 85 private PropertyHalListener mListener; 86 @GuardedBy("mLock") 87 private Set<Integer> mSubscribedHalPropIds; 88 89 private final Object mLock = new Object(); 90 91 /** 92 * Converts manager property ID to Vehicle HAL property ID. 93 */ managerToHalPropId(int mgrPropId)94 private static int managerToHalPropId(int mgrPropId) { 95 return PROPERTY_ID_MANAGER_TO_HAL.getOrDefault(mgrPropId, mgrPropId); 96 } 97 98 /** 99 * Converts Vehicle HAL property ID to manager property ID. 100 */ halToManagerPropId(int halPropId)101 private static int halToManagerPropId(int halPropId) { 102 return PROPERTY_ID_HAL_TO_MANAGER.getOrDefault(halPropId, halPropId); 103 } 104 105 // Checks if the property exists in this VHAL before calling methods in IVehicle. isPropertySupportedInVehicle(int halPropId)106 private boolean isPropertySupportedInVehicle(int halPropId) { 107 return mHalPropIdToVehiclePropConfig.contains(halPropId); 108 } 109 110 /** 111 * PropertyHalListener used to send events to CarPropertyService 112 */ 113 public interface PropertyHalListener { 114 /** 115 * This event is sent whenever the property value is updated 116 * @param events 117 */ onPropertyChange(List<CarPropertyEvent> events)118 void onPropertyChange(List<CarPropertyEvent> events); 119 /** 120 * This event is sent when the set property call fails 121 * @param property 122 * @param area 123 */ onPropertySetError(int property, int area, @CarPropertyManager.CarSetPropertyErrorCode int errorCode)124 void onPropertySetError(int property, int area, 125 @CarPropertyManager.CarSetPropertyErrorCode int errorCode); 126 127 } 128 PropertyHalService(VehicleHal vehicleHal)129 public PropertyHalService(VehicleHal vehicleHal) { 130 mPropIds = new PropertyHalServiceIds(); 131 mSubscribedHalPropIds = new HashSet<Integer>(); 132 mVehicleHal = vehicleHal; 133 if (mDbg) { 134 Slog.d(TAG, "started PropertyHalService"); 135 } 136 } 137 138 /** 139 * Set the listener for the HAL service 140 * @param listener 141 */ setListener(PropertyHalListener listener)142 public void setListener(PropertyHalListener listener) { 143 synchronized (mLock) { 144 mListener = listener; 145 } 146 } 147 148 /** 149 * 150 * @return SparseArray<CarPropertyConfig> List of configs available. 151 */ getPropertyList()152 public SparseArray<CarPropertyConfig<?>> getPropertyList() { 153 if (mDbg) { 154 Slog.d(TAG, "getPropertyList"); 155 } 156 synchronized (mLock) { 157 if (mMgrPropIdToCarPropConfig.size() == 0) { 158 for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) { 159 VehiclePropConfig p = mHalPropIdToVehiclePropConfig.valueAt(i); 160 int mgrPropId = halToManagerPropId(p.prop); 161 CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, mgrPropId); 162 mMgrPropIdToCarPropConfig.put(mgrPropId, config); 163 } 164 } 165 } 166 return mMgrPropIdToCarPropConfig; 167 } 168 169 /** 170 * Returns property or null if property is not ready yet. 171 * @param mgrPropId property id in {@link VehiclePropertyIds} 172 * @param areaId area id 173 * @throws ServiceSpecificException if there is an exception in HAL. 174 */ 175 @Nullable getProperty(int mgrPropId, int areaId)176 public CarPropertyValue getProperty(int mgrPropId, int areaId) throws ServiceSpecificException { 177 int halPropId = managerToHalPropId(mgrPropId); 178 if (!isPropertySupportedInVehicle(halPropId)) { 179 throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId)); 180 } 181 182 // CarPropertyManager catches and rethrows exception, no need to handle here. 183 VehiclePropValue value = mVehicleHal.get(halPropId, areaId); 184 if (value == null) { 185 return null; 186 } 187 if (isMixedTypeProperty(halPropId)) { 188 VehiclePropConfig propConfig; 189 synchronized (mLock) { 190 propConfig = mHalPropIdToVehiclePropConfig.get(halPropId); 191 } 192 boolean containStringType = propConfig.configArray.get(0) == 1; 193 boolean containBooleanType = propConfig.configArray.get(1) == 1; 194 return toMixedCarPropertyValue(value, mgrPropId, containBooleanType, containStringType); 195 } 196 return toCarPropertyValue(value, mgrPropId); 197 } 198 199 /** 200 * Return property or null if property is not ready yet or there is an exception in HAL. 201 */ 202 @Nullable getPropertySafe(int mgrPropId, int areaId)203 public CarPropertyValue getPropertySafe(int mgrPropId, int areaId) { 204 try { 205 return getProperty(mgrPropId, areaId); 206 } catch (Exception e) { 207 Slog.e(TAG, "get property value failed for property id: 0x " 208 + toHexString(mgrPropId) + " area id: 0x" + toHexString(areaId) 209 + " exception: " + e); 210 return null; 211 } 212 } 213 214 /** 215 * Returns sample rate for the property 216 * @param mgrPropId 217 */ getSampleRate(int mgrPropId)218 public float getSampleRate(int mgrPropId) { 219 int halPropId = managerToHalPropId(mgrPropId); 220 if (!isPropertySupportedInVehicle(halPropId)) { 221 throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId)); 222 } 223 return mVehicleHal.getSampleRate(halPropId); 224 } 225 226 /** 227 * Get the read permission string for the property. 228 * @param mgrPropId 229 */ 230 @Nullable getReadPermission(int mgrPropId)231 public String getReadPermission(int mgrPropId) { 232 int halPropId = managerToHalPropId(mgrPropId); 233 return mPropIds.getReadPermission(halPropId); 234 } 235 236 /** 237 * Get the write permission string for the property. 238 * @param mgrPropId 239 */ 240 @Nullable getWritePermission(int mgrPropId)241 public String getWritePermission(int mgrPropId) { 242 int halPropId = managerToHalPropId(mgrPropId); 243 return mPropIds.getWritePermission(halPropId); 244 } 245 246 /** 247 * Get permissions for all properties in the vehicle. 248 * @return a SparseArray. key: propertyId, value: Pair(readPermission, writePermission). 249 */ 250 @NonNull getPermissionsForAllProperties()251 public SparseArray<Pair<String, String>> getPermissionsForAllProperties() { 252 synchronized (mLock) { 253 if (mMgrPropIdToPermissions.size() != 0) { 254 return mMgrPropIdToPermissions; 255 } 256 for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) { 257 int halPropId = mHalPropIdToVehiclePropConfig.keyAt(i); 258 mMgrPropIdToPermissions.put(halToManagerPropId(halPropId), 259 new Pair<>(mPropIds.getReadPermission(halPropId), 260 mPropIds.getWritePermission(halPropId))); 261 } 262 } 263 return mMgrPropIdToPermissions; 264 } 265 266 /** 267 * Return true if property is a display_units property 268 * @param mgrPropId 269 */ isDisplayUnitsProperty(int mgrPropId)270 public boolean isDisplayUnitsProperty(int mgrPropId) { 271 int halPropId = managerToHalPropId(mgrPropId); 272 return mPropIds.isPropertyToChangeUnits(halPropId); 273 } 274 275 /** 276 * Set the property value. 277 * @param prop 278 */ setProperty(CarPropertyValue prop)279 public void setProperty(CarPropertyValue prop) { 280 int halPropId = managerToHalPropId(prop.getPropertyId()); 281 if (!isPropertySupportedInVehicle(halPropId)) { 282 throw new IllegalArgumentException("Invalid property Id : 0x" 283 + toHexString(prop.getPropertyId())); 284 } 285 286 VehiclePropValue halProp; 287 if (isMixedTypeProperty(halPropId)) { 288 // parse mixed type property value. 289 VehiclePropConfig propConfig; 290 synchronized (mLock) { 291 propConfig = mHalPropIdToVehiclePropConfig.get(prop.getPropertyId()); 292 } 293 int[] configArray = propConfig.configArray.stream().mapToInt(i->i).toArray(); 294 halProp = toMixedVehiclePropValue(prop, halPropId, configArray); 295 } else { 296 halProp = toVehiclePropValue(prop, halPropId); 297 } 298 // CarPropertyManager catches and rethrows exception, no need to handle here. 299 mVehicleHal.set(halProp); 300 } 301 302 /** 303 * Subscribe to this property at the specified update rate. 304 * @param mgrPropId 305 * @param rate 306 */ subscribeProperty(int mgrPropId, float rate)307 public void subscribeProperty(int mgrPropId, float rate) { 308 if (mDbg) { 309 Slog.d(TAG, "subscribeProperty propId=0x" + toHexString(mgrPropId) + ", rate=" + rate); 310 } 311 int halPropId = managerToHalPropId(mgrPropId); 312 if (!isPropertySupportedInVehicle(halPropId)) { 313 throw new IllegalArgumentException("Invalid property Id : 0x" 314 + toHexString(mgrPropId)); 315 } 316 synchronized (mLock) { 317 VehiclePropConfig cfg = mHalPropIdToVehiclePropConfig.get(halPropId); 318 if (rate > cfg.maxSampleRate) { 319 rate = cfg.maxSampleRate; 320 } else if (rate < cfg.minSampleRate) { 321 rate = cfg.minSampleRate; 322 } 323 mSubscribedHalPropIds.add(halPropId); 324 } 325 326 mVehicleHal.subscribeProperty(this, halPropId, rate); 327 } 328 329 /** 330 * Unsubscribe the property and turn off update events for it. 331 * @param mgrPropId 332 */ unsubscribeProperty(int mgrPropId)333 public void unsubscribeProperty(int mgrPropId) { 334 if (mDbg) { 335 Slog.d(TAG, "unsubscribeProperty propId=0x" + toHexString(mgrPropId)); 336 } 337 int halPropId = managerToHalPropId(mgrPropId); 338 if (!isPropertySupportedInVehicle(halPropId)) { 339 throw new IllegalArgumentException("Invalid property Id : 0x" 340 + toHexString(mgrPropId)); 341 } 342 synchronized (mLock) { 343 if (mSubscribedHalPropIds.contains(halPropId)) { 344 mSubscribedHalPropIds.remove(halPropId); 345 mVehicleHal.unsubscribeProperty(this, halPropId); 346 } 347 } 348 } 349 350 @Override init()351 public void init() { 352 if (mDbg) { 353 Slog.d(TAG, "init()"); 354 } 355 } 356 357 @Override release()358 public void release() { 359 if (mDbg) { 360 Slog.d(TAG, "release()"); 361 } 362 synchronized (mLock) { 363 for (Integer halProp : mSubscribedHalPropIds) { 364 mVehicleHal.unsubscribeProperty(this, halProp); 365 } 366 mSubscribedHalPropIds.clear(); 367 mHalPropIdToVehiclePropConfig.clear(); 368 mMgrPropIdToCarPropConfig.clear(); 369 mMgrPropIdToPermissions.clear(); 370 mListener = null; 371 } 372 } 373 374 @Override isSupportedProperty(int propId)375 public boolean isSupportedProperty(int propId) { 376 return mPropIds.isSupportedProperty(propId); 377 } 378 379 @Override getAllSupportedProperties()380 public int[] getAllSupportedProperties() { 381 return CarServiceUtils.EMPTY_INT_ARRAY; 382 } 383 384 // The method is called in HAL init(). Avoid handling complex things in here. 385 @Override takeProperties(Collection<VehiclePropConfig> allProperties)386 public void takeProperties(Collection<VehiclePropConfig> allProperties) { 387 for (VehiclePropConfig p : allProperties) { 388 if (mPropIds.isSupportedProperty(p.prop)) { 389 synchronized (mLock) { 390 mHalPropIdToVehiclePropConfig.put(p.prop, p); 391 } 392 if (mDbg) { 393 Slog.d(TAG, "takeSupportedProperties: " + toHexString(p.prop)); 394 } 395 } 396 } 397 if (mDbg) { 398 Slog.d(TAG, "takeSupportedProperties() took " + allProperties.size() 399 + " properties"); 400 } 401 // If vehicle hal support to select permission for vendor properties. 402 VehiclePropConfig customizePermission; 403 synchronized (mLock) { 404 customizePermission = mHalPropIdToVehiclePropConfig.get( 405 VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION); 406 } 407 if (customizePermission != null) { 408 mPropIds.customizeVendorPermission(customizePermission.configArray); 409 } 410 } 411 412 @Override onHalEvents(List<VehiclePropValue> values)413 public void onHalEvents(List<VehiclePropValue> values) { 414 PropertyHalListener listener; 415 synchronized (mLock) { 416 listener = mListener; 417 } 418 if (listener != null) { 419 for (VehiclePropValue v : values) { 420 if (v == null) { 421 continue; 422 } 423 if (!isPropertySupportedInVehicle(v.prop)) { 424 Slog.e(TAG, "Property is not supported: 0x" + toHexString(v.prop)); 425 continue; 426 } 427 // Check payload if it is a userdebug build. 428 if (Build.IS_DEBUGGABLE && !mPropIds.checkPayload(v)) { 429 Slog.e(TAG, "Drop event for property: " + v + " because it is failed " 430 + "in payload checking."); 431 continue; 432 } 433 int mgrPropId = halToManagerPropId(v.prop); 434 CarPropertyValue<?> propVal; 435 if (isMixedTypeProperty(v.prop)) { 436 // parse mixed type property value. 437 VehiclePropConfig propConfig; 438 synchronized (mLock) { 439 propConfig = mHalPropIdToVehiclePropConfig.get(v.prop); 440 } 441 boolean containStringType = propConfig.configArray.get(0) == 1; 442 boolean containBooleanType = propConfig.configArray.get(1) == 1; 443 propVal = toMixedCarPropertyValue(v, mgrPropId, containBooleanType, 444 containStringType); 445 } else { 446 propVal = toCarPropertyValue(v, mgrPropId); 447 } 448 CarPropertyEvent event = new CarPropertyEvent( 449 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal); 450 mEventsToDispatch.add(event); 451 } 452 listener.onPropertyChange(mEventsToDispatch); 453 mEventsToDispatch.clear(); 454 } 455 } 456 457 @Override onPropertySetError(int halPropId, int area, @CarPropertyManager.CarSetPropertyErrorCode int errorCode)458 public void onPropertySetError(int halPropId, int area, 459 @CarPropertyManager.CarSetPropertyErrorCode int errorCode) { 460 PropertyHalListener listener; 461 synchronized (mLock) { 462 listener = mListener; 463 } 464 if (listener != null) { 465 int mgrPropId = halToManagerPropId(halPropId); 466 listener.onPropertySetError(mgrPropId, area, errorCode); 467 } 468 } 469 470 @Override 471 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(PrintWriter writer)472 public void dump(PrintWriter writer) { 473 writer.println(TAG); 474 writer.println(" Properties available:"); 475 synchronized (mLock) { 476 for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) { 477 VehiclePropConfig p = mHalPropIdToVehiclePropConfig.valueAt(i); 478 writer.println(" " + p); 479 } 480 } 481 } 482 isMixedTypeProperty(int halPropId)483 private static boolean isMixedTypeProperty(int halPropId) { 484 return (halPropId & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED; 485 } 486 } 487