1 /* 2 * Copyright (C) 2021 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; 18 19 import static com.android.car.CarServiceUtils.subscribeOptionsToHidl; 20 import static com.android.car.internal.property.CarPropertyErrorCodes.convertVhalStatusCodeToCarPropertyManagerErrorCodes; 21 22 import android.annotation.Nullable; 23 import android.car.builtin.util.Slogf; 24 import android.car.hardware.property.CarPropertyManager; 25 import android.hardware.automotive.vehicle.SubscribeOptions; 26 import android.hardware.automotive.vehicle.V2_0.IVehicle; 27 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback; 28 import android.hardware.automotive.vehicle.V2_0.StatusCode; 29 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 30 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 31 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyStatus; 32 import android.hardware.automotive.vehicle.VehiclePropError; 33 import android.os.NativeHandle; 34 import android.os.RemoteException; 35 import android.os.ServiceSpecificException; 36 import android.os.SystemProperties; 37 38 import com.android.car.hal.HalPropConfig; 39 import com.android.car.hal.HalPropValue; 40 import com.android.car.hal.HalPropValueBuilder; 41 import com.android.car.hal.HidlHalPropConfig; 42 import com.android.car.hal.VehicleHalCallback; 43 import com.android.car.internal.property.CarPropertyErrorCodes; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.io.FileDescriptor; 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.NoSuchElementException; 50 import java.util.concurrent.Executor; 51 import java.util.concurrent.Executors; 52 53 final class HidlVehicleStub extends VehicleStub { 54 55 private static final String TAG = CarLog.tagFor(HidlVehicleStub.class); 56 57 // The property ID for "SUPPORTED_PROPRETY_IDS". This is the same as SUPPORTED_PROPERTY_IDS as 58 // defined in 59 // {@code platform/hardware/interfaces/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleProperty.aidl}. 60 private static final int VHAL_PROP_SUPPORTED_PROPERTY_IDS = 0x11410F48; 61 62 private final IVehicle mHidlVehicle; 63 private final HalPropValueBuilder mPropValueBuilder; 64 private final Executor mExecutor = Executors.newFixedThreadPool(5); 65 HidlVehicleStub()66 HidlVehicleStub() { 67 this(getHidlVehicle()); 68 } 69 70 @VisibleForTesting HidlVehicleStub(IVehicle hidlVehicle)71 HidlVehicleStub(IVehicle hidlVehicle) { 72 mHidlVehicle = hidlVehicle; 73 mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/false); 74 } 75 76 /** 77 * Checks whether we are connected to AIDL VHAL: {@code true} or HIDL VHAL: {@code false}. 78 */ 79 @Override isAidlVhal()80 public boolean isAidlVhal() { 81 return false; 82 } 83 84 /** 85 * Gets a HalPropValueBuilder that could be used to build a HalPropValue. 86 * 87 * @return a builder to build HalPropValue. 88 */ 89 @Override getHalPropValueBuilder()90 public HalPropValueBuilder getHalPropValueBuilder() { 91 return mPropValueBuilder; 92 } 93 94 /** 95 * Returns whether this vehicle stub is connecting to a valid vehicle HAL. 96 * 97 * @return Whether this vehicle stub is connecting to a valid vehicle HAL. 98 */ 99 @Override isValid()100 public boolean isValid() { 101 return mHidlVehicle != null; 102 } 103 104 /** 105 * Gets the interface descriptor for the connecting vehicle HAL. 106 * 107 * @return the interface descriptor. 108 * @throws IllegalStateException If unable to get the descriptor. 109 */ 110 @Override getInterfaceDescriptor()111 public String getInterfaceDescriptor() throws IllegalStateException { 112 try { 113 return mHidlVehicle.interfaceDescriptor(); 114 } catch (RemoteException e) { 115 throw new IllegalStateException("Unable to get Vehicle HAL interface descriptor", e); 116 } 117 } 118 119 /** 120 * Registers a death recipient that would be called when vehicle HAL died. 121 * 122 * @param recipient A death recipient. 123 * @throws IllegalStateException If unable to register the death recipient. 124 */ 125 @Override linkToDeath(IVehicleDeathRecipient recipient)126 public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException { 127 try { 128 mHidlVehicle.linkToDeath(recipient, /* cookie= */ 0); 129 } catch (RemoteException e) { 130 throw new IllegalStateException("Failed to linkToDeath Vehicle HAL"); 131 } 132 } 133 134 /** 135 * Unlinks a previously linked death recipient. 136 * 137 * @param recipient A previously linked death recipient. 138 */ 139 @Override unlinkToDeath(IVehicleDeathRecipient recipient)140 public void unlinkToDeath(IVehicleDeathRecipient recipient) { 141 try { 142 mHidlVehicle.unlinkToDeath(recipient); 143 } catch (RemoteException ignored) { 144 // Ignore errors on shutdown path. 145 } 146 } 147 148 /** 149 * Gets all property configs. 150 * 151 * @return All the property configs. 152 */ 153 @Override getAllPropConfigs()154 public HalPropConfig[] getAllPropConfigs() throws RemoteException { 155 ArrayList<VehiclePropConfig> configForSupportedProps; 156 try { 157 configForSupportedProps = getPropConfigs(new ArrayList<>( 158 List.of(VHAL_PROP_SUPPORTED_PROPERTY_IDS))); 159 } catch (Exception e) { 160 Slogf.d(TAG, "Use getAllPropConfigs to fetch all property configs"); 161 162 // If the VHAL_PROP_SUPPORTED_PROPERTY_IDS is not supported, fallback to normal API. 163 return vehiclePropConfigsToHalPropConfigs(mHidlVehicle.getAllPropConfigs()); 164 } 165 166 if (configForSupportedProps.size() == 0) { 167 Slogf.w(TAG, "getPropConfigs[VHAL_PROP_SUPPORTED_IDS] returns 0 config" 168 + "assume it is not supported, fall back to getAllPropConfigs."); 169 return vehiclePropConfigsToHalPropConfigs(mHidlVehicle.getAllPropConfigs()); 170 } 171 172 // If the VHAL_PROP_SUPPORTED_PROPERTY_IDS is supported, VHAL has 173 // too many property configs that cannot be returned in getAllPropConfigs() in one binder 174 // transaction. 175 // We need to get the property list and then divide the list into smaller requests. 176 Slogf.d(TAG, "VHAL_PROP_SUPPORTED_PROPERTY_IDS is supported, " 177 + "use multiple getPropConfigs to fetch all property configs"); 178 179 return getAllPropConfigsThroughMultipleRequests(configForSupportedProps.get(0)); 180 } 181 182 /** 183 * Gets a new {@code SubscriptionClient} that could be used to subscribe/unsubscribe. 184 * 185 * @param callback A callback that could be used to receive events. 186 * @return a {@code SubscriptionClient} that could be used to subscribe/unsubscribe. 187 */ 188 @Override newSubscriptionClient(VehicleHalCallback callback)189 public SubscriptionClient newSubscriptionClient(VehicleHalCallback callback) { 190 return new HidlSubscriptionClient(callback, mPropValueBuilder); 191 } 192 193 private static class GetValueResult { 194 public int status; 195 public VehiclePropValue value; 196 } 197 198 /** 199 * Gets a property. 200 * 201 * @param requestedPropValue The property to get. 202 * @return The vehicle property value. 203 * @throws RemoteException if the remote operation fails. 204 * @throws ServiceSpecificException if VHAL returns service specific error. 205 */ 206 @Override 207 @Nullable get(HalPropValue requestedPropValue)208 public HalPropValue get(HalPropValue requestedPropValue) 209 throws RemoteException, ServiceSpecificException { 210 VehiclePropValue hidlPropValue = (VehiclePropValue) requestedPropValue.toVehiclePropValue(); 211 GetValueResult result = new GetValueResult(); 212 mHidlVehicle.get( 213 hidlPropValue, 214 (s, p) -> { 215 result.status = s; 216 result.value = p; 217 }); 218 219 if (result.status != android.hardware.automotive.vehicle.V2_0.StatusCode.OK) { 220 throw new ServiceSpecificException( 221 result.status, 222 "failed to get value for property: " + Integer.toString(hidlPropValue.prop)); 223 } 224 225 if (result.value == null) { 226 return null; 227 } 228 229 return getHalPropValueBuilder().build(result.value); 230 } 231 232 @Override getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, VehicleStubCallbackInterface getVehicleStubAsyncCallback)233 public void getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, 234 VehicleStubCallbackInterface getVehicleStubAsyncCallback) { 235 mExecutor.execute(() -> { 236 for (int i = 0; i < getVehicleStubAsyncRequests.size(); i++) { 237 AsyncGetSetRequest getVehicleStubAsyncRequest = getVehicleStubAsyncRequests.get(i); 238 int serviceRequestId = getVehicleStubAsyncRequest.getServiceRequestId(); 239 HalPropValue halPropValue; 240 try { 241 halPropValue = get(getVehicleStubAsyncRequest.getHalPropValue()); 242 } catch (ServiceSpecificException e) { 243 CarPropertyErrorCodes carPropertyErrorCodes = 244 convertVhalStatusCodeToCarPropertyManagerErrorCodes(e.errorCode); 245 callGetAsyncErrorCallback(carPropertyErrorCodes, serviceRequestId, 246 getVehicleStubAsyncCallback); 247 continue; 248 } catch (RemoteException e) { 249 Slogf.w(CarLog.TAG_SERVICE, 250 "Received RemoteException from VHAL. VHAL is likely dead."); 251 callGetAsyncErrorCallback( 252 new CarPropertyErrorCodes( 253 CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR, 254 /* vendorErrorCode= */ 0, 255 /* systemErrorCode= */ 0), 256 serviceRequestId, getVehicleStubAsyncCallback); 257 continue; 258 } 259 260 if (halPropValue == null) { 261 callGetAsyncErrorCallback( 262 new CarPropertyErrorCodes( 263 CarPropertyManager.STATUS_ERROR_NOT_AVAILABLE, 264 /* vendorErrorCode= */ 0, 265 /* systemErrorCode= */ 0), 266 serviceRequestId, getVehicleStubAsyncCallback); 267 continue; 268 } 269 270 getVehicleStubAsyncCallback.onGetAsyncResults( 271 List.of(new GetVehicleStubAsyncResult(serviceRequestId, halPropValue))); 272 } 273 }); 274 } 275 276 @Override setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, VehicleStubCallbackInterface setVehicleStubAsyncCallback)277 public void setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, 278 VehicleStubCallbackInterface setVehicleStubAsyncCallback) { 279 mExecutor.execute(() -> { 280 for (int i = 0; i < setVehicleStubAsyncRequests.size(); i++) { 281 AsyncGetSetRequest setVehicleStubAsyncRequest = setVehicleStubAsyncRequests.get(i); 282 int serviceRequestId = setVehicleStubAsyncRequest.getServiceRequestId(); 283 try { 284 set(setVehicleStubAsyncRequest.getHalPropValue()); 285 setVehicleStubAsyncCallback.onSetAsyncResults( 286 List.of(new SetVehicleStubAsyncResult(serviceRequestId))); 287 } catch (ServiceSpecificException e) { 288 CarPropertyErrorCodes carPropertyErrorCodes = 289 convertVhalStatusCodeToCarPropertyManagerErrorCodes(e.errorCode); 290 callSetAsyncErrorCallback( 291 carPropertyErrorCodes, 292 serviceRequestId, 293 setVehicleStubAsyncCallback); 294 } catch (RemoteException e) { 295 Slogf.w(CarLog.TAG_SERVICE, 296 "Received RemoteException from VHAL. VHAL is likely dead."); 297 callSetAsyncErrorCallback( 298 new CarPropertyErrorCodes( 299 CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR, 300 /* vendorErrorCode= */ 0, 301 /* systemErrorCode= */ 0), 302 serviceRequestId, setVehicleStubAsyncCallback); 303 } 304 } 305 }); 306 } 307 callGetAsyncErrorCallback(CarPropertyErrorCodes errorCodes, int serviceRequestId, VehicleStubCallbackInterface callback)308 private void callGetAsyncErrorCallback(CarPropertyErrorCodes errorCodes, int serviceRequestId, 309 VehicleStubCallbackInterface callback) { 310 callback.onGetAsyncResults( 311 List.of(new GetVehicleStubAsyncResult(serviceRequestId, errorCodes))); 312 } 313 callSetAsyncErrorCallback(CarPropertyErrorCodes errorCodes, int serviceRequestId, VehicleStubCallbackInterface callback)314 private void callSetAsyncErrorCallback(CarPropertyErrorCodes errorCodes, int serviceRequestId, 315 VehicleStubCallbackInterface callback) { 316 callback.onSetAsyncResults( 317 List.of(new SetVehicleStubAsyncResult(serviceRequestId, errorCodes))); 318 } 319 320 /** 321 * Sets a property. 322 * 323 * @param propValue The property to set. 324 * @throws RemoteException if the remote operation fails. 325 * @throws ServiceSpecificException if VHAL returns service specific error. 326 */ 327 @Override set(HalPropValue propValue)328 public void set(HalPropValue propValue) throws RemoteException { 329 VehiclePropValue hidlPropValue = (VehiclePropValue) propValue.toVehiclePropValue(); 330 int status = mHidlVehicle.set(hidlPropValue); 331 if (status != StatusCode.OK) { 332 throw new ServiceSpecificException(status, "failed to set value for property: " 333 + Integer.toString(hidlPropValue.prop)); 334 } 335 } 336 337 @Override dump(FileDescriptor fd, List<String> args)338 public void dump(FileDescriptor fd, List<String> args) throws RemoteException { 339 mHidlVehicle.debug(new NativeHandle(fd, /* own= */ false), new ArrayList<String>(args)); 340 } 341 342 @Nullable getHidlVehicle()343 private static IVehicle getHidlVehicle() { 344 String instanceName = SystemProperties.get("ro.vehicle.hal", "default"); 345 346 try { 347 // Wait for HIDL VHAL to be ready if it is declared. 348 return IVehicle.getService(instanceName, /* retry= */ true); 349 } catch (RemoteException e) { 350 Slogf.e(TAG, e, "Failed to get IVehicle/" + instanceName + " service"); 351 } catch (NoSuchElementException e) { 352 Slogf.e(TAG, "IVehicle/" + instanceName + " service not registered yet"); 353 } 354 return null; 355 } 356 357 private class HidlSubscriptionClient extends IVehicleCallback.Stub 358 implements SubscriptionClient { 359 private final VehicleHalCallback mCallback; 360 private final HalPropValueBuilder mBuilder; 361 HidlSubscriptionClient(VehicleHalCallback callback, HalPropValueBuilder builder)362 HidlSubscriptionClient(VehicleHalCallback callback, HalPropValueBuilder builder) { 363 mCallback = callback; 364 mBuilder = builder; 365 } 366 367 @Override onPropertyEvent(ArrayList<VehiclePropValue> propValues)368 public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) { 369 ArrayList<HalPropValue> values = new ArrayList<>(); 370 for (VehiclePropValue value : propValues) { 371 values.add(mBuilder.build(value)); 372 } 373 mCallback.onPropertyEvent(values); 374 } 375 376 @Override onPropertySet(VehiclePropValue propValue)377 public void onPropertySet(VehiclePropValue propValue) { 378 // Deprecated, do nothing. 379 } 380 381 @Override onPropertySetError(int errorCode, int propId, int areaId)382 public void onPropertySetError(int errorCode, int propId, int areaId) { 383 VehiclePropError error = new VehiclePropError(); 384 error.propId = propId; 385 error.areaId = areaId; 386 error.errorCode = errorCode; 387 ArrayList<VehiclePropError> errors = new ArrayList<VehiclePropError>(); 388 errors.add(error); 389 mCallback.onPropertySetError(errors); 390 } 391 392 @Override subscribe(SubscribeOptions[] options)393 public void subscribe(SubscribeOptions[] options) throws RemoteException { 394 ArrayList<android.hardware.automotive.vehicle.V2_0.SubscribeOptions> hidlOptions = 395 new ArrayList<>(); 396 for (SubscribeOptions option : options) { 397 hidlOptions.add(subscribeOptionsToHidl(option)); 398 } 399 mHidlVehicle.subscribe(this, hidlOptions); 400 } 401 402 @Override unsubscribe(int prop)403 public void unsubscribe(int prop) throws RemoteException { 404 mHidlVehicle.unsubscribe(this, prop); 405 } 406 } 407 vehiclePropConfigsToHalPropConfigs( List<VehiclePropConfig> hidlConfigs)408 private static HalPropConfig[] vehiclePropConfigsToHalPropConfigs( 409 List<VehiclePropConfig> hidlConfigs) { 410 int configSize = hidlConfigs.size(); 411 HalPropConfig[] configs = new HalPropConfig[configSize]; 412 for (int i = 0; i < configSize; i++) { 413 configs[i] = new HidlHalPropConfig(hidlConfigs.get(i)); 414 } 415 return configs; 416 } 417 418 private static final class GetPropConfigsResult { 419 public int status; 420 public ArrayList<VehiclePropConfig> propConfigs; 421 } 422 getAllPropConfigsThroughMultipleRequests( VehiclePropConfig configForSupportedProps)423 private HalPropConfig[] getAllPropConfigsThroughMultipleRequests( 424 VehiclePropConfig configForSupportedProps) 425 throws RemoteException, ServiceSpecificException { 426 if (configForSupportedProps.configArray.size() < 1) { 427 throw new IllegalArgumentException( 428 "VHAL Property: SUPPORTED_PROPERTY_IDS must have one element: " 429 + "[num_of_configs_per_request] in the config array"); 430 } 431 432 int numConfigsPerRequest = configForSupportedProps.configArray.get(0); 433 if (numConfigsPerRequest <= 0) { 434 throw new IllegalArgumentException("Number of configs per request must be > 0"); 435 } 436 HalPropValue propIdsRequestValue = mPropValueBuilder.build( 437 VHAL_PROP_SUPPORTED_PROPERTY_IDS, /* areaId= */ 0); 438 HalPropValue propIdsResultValue; 439 try { 440 propIdsResultValue = get(propIdsRequestValue); 441 } catch (Exception e) { 442 Slogf.e(TAG, e, "failed to get SUPPORTED_PROPRETY_IDS"); 443 throw e; 444 } 445 int status = propIdsResultValue.getStatus(); 446 if (status != VehiclePropertyStatus.AVAILABLE) { 447 throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, 448 "got non-okay status: " + StatusCode.toString(status) 449 + " for SUPPORTED_PROPERTY_IDS"); 450 } 451 int propCount = propIdsResultValue.getInt32ValuesSize(); 452 ArrayList<VehiclePropConfig> allConfigs = new ArrayList<>(); 453 ArrayList<Integer> requestPropIds = new ArrayList<Integer>(); 454 for (int i = 0; i < propCount; i++) { 455 requestPropIds.add(propIdsResultValue.getInt32Value(i)); 456 if (requestPropIds.size() == numConfigsPerRequest || (i + 1) == propCount) { 457 ArrayList<VehiclePropConfig> subConfigs = getPropConfigs(requestPropIds); 458 allConfigs.addAll(subConfigs); 459 requestPropIds.clear(); 460 } 461 } 462 return vehiclePropConfigsToHalPropConfigs(allConfigs); 463 } 464 getPropConfigs(ArrayList<Integer> propIds)465 private ArrayList<VehiclePropConfig> getPropConfigs(ArrayList<Integer> propIds) 466 throws RemoteException { 467 GetPropConfigsResult result = new GetPropConfigsResult(); 468 mHidlVehicle.getPropConfigs(propIds, 469 (status, propConfigs) -> { 470 result.status = status; 471 result.propConfigs = propConfigs; 472 }); 473 if (result.status != StatusCode.OK) { 474 throw new IllegalArgumentException("Part of the property IDs: " + propIds 475 + " is not supported"); 476 } 477 return result.propConfigs; 478 } 479 } 480