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