• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.car.hardware.property.CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED;
19 import static android.car.hardware.property.CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG;
20 import static android.car.hardware.property.CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE;
21 import static android.car.hardware.property.CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN;
22 import static android.car.hardware.property.CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN;
23 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_ACCESS_DENIED;
24 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_INTERNAL_ERROR;
25 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_INVALID_ARG;
26 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_NOT_AVAILABLE;
27 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_NOT_AVAILABLE_DISABLED;
28 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_NOT_AVAILABLE_POOR_VISIBILITY;
29 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SAFETY;
30 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_HIGH;
31 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_NOT_AVAILABLE_SPEED_LOW;
32 import static android.car.hardware.property.VehicleHalStatusCode.STATUS_TRY_AGAIN;
33 
34 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
35 import static com.android.car.internal.property.CarPropertyHelper.STATUS_OK;
36 
37 import android.annotation.IntDef;
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.car.VehiclePropertyIds;
41 import android.car.builtin.os.BuildHelper;
42 import android.car.builtin.util.Slogf;
43 import android.car.hardware.CarPropertyConfig;
44 import android.car.hardware.CarPropertyValue;
45 import android.car.hardware.property.CarPropertyEvent;
46 import android.car.hardware.property.CarPropertyManager;
47 import android.car.hardware.property.CarPropertyManager.CarPropertyAsyncErrorCode;
48 import android.car.hardware.property.CarPropertyManager.CarSetPropertyErrorCode;
49 import android.car.hardware.property.VehicleHalStatusCode.VehicleHalStatusCodeInt;
50 import android.hardware.automotive.vehicle.VehiclePropError;
51 import android.hardware.automotive.vehicle.VehicleProperty;
52 import android.hardware.automotive.vehicle.VehiclePropertyStatus;
53 import android.os.Handler;
54 import android.os.HandlerThread;
55 import android.os.IBinder;
56 import android.os.IBinder.DeathRecipient;
57 import android.os.RemoteException;
58 import android.os.ServiceSpecificException;
59 import android.os.SystemClock;
60 import android.util.ArrayMap;
61 import android.util.ArraySet;
62 import android.util.Log;
63 import android.util.Pair;
64 import android.util.SparseArray;
65 
66 import com.android.car.CarLog;
67 import com.android.car.CarServiceUtils;
68 import com.android.car.VehicleStub;
69 import com.android.car.VehicleStub.AsyncGetSetRequest;
70 import com.android.car.VehicleStub.GetVehicleStubAsyncResult;
71 import com.android.car.VehicleStub.SetVehicleStubAsyncResult;
72 import com.android.car.VehicleStub.VehicleStubCallbackInterface;
73 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
74 import com.android.car.internal.LongPendingRequestPool;
75 import com.android.car.internal.LongPendingRequestPool.TimeoutCallback;
76 import com.android.car.internal.LongRequestIdWithTimeout;
77 import com.android.car.internal.property.AsyncPropertyServiceRequest;
78 import com.android.car.internal.property.CarPropertyHelper;
79 import com.android.car.internal.property.GetSetValueResult;
80 import com.android.car.internal.property.GetSetValueResultList;
81 import com.android.car.internal.property.IAsyncPropertyResultCallback;
82 import com.android.internal.annotations.GuardedBy;
83 
84 import java.io.PrintWriter;
85 import java.lang.annotation.Retention;
86 import java.lang.annotation.RetentionPolicy;
87 import java.util.ArrayList;
88 import java.util.Collection;
89 import java.util.List;
90 import java.util.Map;
91 import java.util.Set;
92 import java.util.concurrent.atomic.AtomicInteger;
93 import java.util.function.Function;
94 
95 /**
96  * Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty.
97  * Services that communicate by passing vehicle properties back and forth via ICarProperty should
98  * extend this class.
99  */
100 public class PropertyHalService extends HalServiceBase {
101     private static final String TAG = CarLog.tagFor(PropertyHalService.class);
102     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
103     private static final int ASYNC_RETRY_SLEEP_IN_MS = 100;
104 
105     // Async get request from user.
106     private static final int GET = 0;
107     // Async set request from user.
108     private static final int SET = 1;
109     // Async get request for getting initial value when user issues async set property request.
110     // The reason we need to get initial value is that if the value to be set is the same as
111     // the current value, there might not be a property update event generated. In this case,
112     // it should be considered a success. If we get the initial value successfully and the
113     // initial value is the same as the target value, we treat the async set as success.
114     private static final int GET_INITIAL_VALUE_FOR_SET = 2;
115 
116     // Different type of async get/set property requests.
117     @IntDef({GET, SET, GET_INITIAL_VALUE_FOR_SET})
118     @Retention(RetentionPolicy.SOURCE)
119     private @interface AsyncRequestType {}
120 
121     private static final class AsyncPropRequestInfo implements LongRequestIdWithTimeout {
122         private final AsyncPropertyServiceRequest mPropMgrRequest;
123         // The uptimeMillis when this request time out.
124         private final long mTimeoutUptimeMs;
125         private final @AsyncRequestType int mRequestType;
126         private final VehicleStubCallback mVehicleStubCallback;
127         private boolean mSetRequestSent;
128         private long mUpdateTimestampNanos;
129         private boolean mValueUpdated;
130         private int mServiceRequestId;
131         private float mUpdateRateHz;
132         // The associated async set request for get_initial_value request.
133         private @Nullable AsyncPropRequestInfo mAssocSetValueRequestInfo;
134         // The associated get initial value request for async set request.
135         private @Nullable AsyncPropRequestInfo mAssocGetInitValueRequestInfo;
136 
AsyncPropRequestInfo(@syncRequestType int requestType, AsyncPropertyServiceRequest propMgrRequest, long timeoutUptimeMs, VehicleStubCallback vehicleStubCallback)137         AsyncPropRequestInfo(@AsyncRequestType int requestType,
138                 AsyncPropertyServiceRequest propMgrRequest,
139                 long timeoutUptimeMs, VehicleStubCallback vehicleStubCallback) {
140             mPropMgrRequest = propMgrRequest;
141             mTimeoutUptimeMs = timeoutUptimeMs;
142             mRequestType = requestType;
143             mVehicleStubCallback = vehicleStubCallback;
144         }
145 
getRequestType()146         private @AsyncRequestType int getRequestType() {
147             return mRequestType;
148         }
149 
getManagerRequestId()150         private int getManagerRequestId() {
151             return mPropMgrRequest.getRequestId();
152         }
153 
154 
getPropertyName()155         private String getPropertyName() {
156             return VehiclePropertyIds.toString(getPropertyId());
157         }
158 
requestTypeToString(@syncRequestType int requestType)159         private static String requestTypeToString(@AsyncRequestType int requestType) {
160             switch (requestType) {
161                 case GET:
162                     return "GET";
163                 case SET:
164                     return "SET";
165                 case GET_INITIAL_VALUE_FOR_SET:
166                     return "GET_INITIAL_VALUE_FOR_SET";
167                 default:
168                     return "UNKNOWN";
169             }
170         }
171 
getPropertyId()172         int getPropertyId() {
173             return mPropMgrRequest.getPropertyId();
174         }
175 
getAreaId()176         int getAreaId() {
177             return mPropMgrRequest.getAreaId();
178         }
179 
getUpdateTimestampNanos()180         public long getUpdateTimestampNanos() {
181             return mUpdateTimestampNanos;
182         }
183 
getPropSvcRequest()184         AsyncPropertyServiceRequest getPropSvcRequest() {
185             return mPropMgrRequest;
186         }
187 
toErrorResult(@arPropertyAsyncErrorCode int errorCode, int vendorErrorCode)188         GetSetValueResult toErrorResult(@CarPropertyAsyncErrorCode int errorCode,
189                 int vendorErrorCode) {
190             return GetSetValueResult.newErrorResult(getManagerRequestId(), errorCode,
191                     vendorErrorCode);
192         }
193 
toGetValueResult(CarPropertyValue value)194         GetSetValueResult toGetValueResult(CarPropertyValue value) {
195             return GetSetValueResult.newGetValueResult(getManagerRequestId(), value);
196         }
197 
toSetValueResult(long updateTimestampNanos)198         GetSetValueResult toSetValueResult(long updateTimestampNanos) {
199             return GetSetValueResult.newSetValueResult(getManagerRequestId(),
200                     updateTimestampNanos);
201         }
202 
setSetRequestSent()203         void setSetRequestSent() {
204             mSetRequestSent = true;
205         }
206 
setValueUpdated(long updateTimestampNanos)207         void setValueUpdated(long updateTimestampNanos) {
208             mValueUpdated = true;
209             mUpdateTimestampNanos = updateTimestampNanos;
210         }
211 
isWaitForPropertyUpdate()212         boolean isWaitForPropertyUpdate() {
213             return mPropMgrRequest.isWaitForPropertyUpdate();
214         }
215 
success()216         boolean success() {
217             // If the set request is sent and either we don't wait for property update or the
218             // property update happened (which includes the initial value is already the target
219             // value)
220             return mSetRequestSent && (!isWaitForPropertyUpdate() || mValueUpdated);
221         }
222 
setAssocSetValueRequestInfo(AsyncPropRequestInfo requestInfo)223         void setAssocSetValueRequestInfo(AsyncPropRequestInfo requestInfo) {
224             mAssocSetValueRequestInfo = requestInfo;
225         }
226 
getAssocSetValueRequestInfo()227         @Nullable AsyncPropRequestInfo getAssocSetValueRequestInfo() {
228             return mAssocSetValueRequestInfo;
229         }
230 
setAssocGetInitValueRequestInfo(AsyncPropRequestInfo requestInfo)231         void setAssocGetInitValueRequestInfo(AsyncPropRequestInfo requestInfo) {
232             mAssocGetInitValueRequestInfo = requestInfo;
233         }
234 
getAssocGetInitValueRequestInfo()235         @Nullable AsyncPropRequestInfo getAssocGetInitValueRequestInfo() {
236             return mAssocGetInitValueRequestInfo;
237         }
238 
setServiceRequestId(int serviceRequestId)239         void setServiceRequestId(int serviceRequestId) {
240             mServiceRequestId = serviceRequestId;
241         }
242 
getServiceRequestId()243         int getServiceRequestId() {
244             return mServiceRequestId;
245         }
246 
getVehicleStubCallback()247         VehicleStubCallback getVehicleStubCallback() {
248             return mVehicleStubCallback;
249         }
250 
getUpdateRateHz()251         float getUpdateRateHz() {
252             return mUpdateRateHz;
253         }
254 
255         /**
256          * Parses the updateRateHz from client and sanitize it.
257          */
parseClientUpdateRateHz(HalPropConfig halPropConfig)258         void parseClientUpdateRateHz(HalPropConfig halPropConfig) {
259             float clientUpdateRateHz = mPropMgrRequest.getUpdateRateHz();
260             if (clientUpdateRateHz == 0.0f) {
261                 // If client does not specify a sample rate for async set, subscribe at the max
262                 // sample rate so that we can get the property update as soon as possible.
263                 clientUpdateRateHz = halPropConfig.getMaxSampleRate();
264             }
265             mUpdateRateHz = sanitizeUpdateRateHz(clientUpdateRateHz, halPropConfig);
266         }
267 
268         @Override
getTimeoutUptimeMs()269         public long getTimeoutUptimeMs() {
270             return mTimeoutUptimeMs;
271         }
272 
273         @Override
getRequestId()274         public long getRequestId() {
275             return getServiceRequestId();
276         }
277 
278         @Override
279         @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()280         public String toString() {
281             return new StringBuilder()
282                     .append("AsyncPropRequestInfo{type: ")
283                     .append(requestTypeToString(mRequestType))
284                     .append(", mgrRequestId: ")
285                     .append(getManagerRequestId())
286                     .append(", property: ")
287                     .append(getPropertyName())
288                     .append(", areaId: ")
289                     .append(getAreaId())
290                     .append(", timeout at uptime: ")
291                     .append(getTimeoutUptimeMs()).append("ms")
292                     .append(", serviceRequestId: ")
293                     .append(getServiceRequestId())
294                     .append(", update rate: ")
295                     .append(getUpdateRateHz()).append("hz")
296                     .append(", value updated for set: ")
297                     .append(mValueUpdated)
298                     .append(", request sent for set: ")
299                     .append(mSetRequestSent)
300                     .append("}").toString();
301         }
302     };
303 
304     // The request ID passed by CarPropertyService (ManagerRequestId) is directly passed from
305     // CarPropertyManager. Multiple CarPropertyManagers use the same car service instance, thus,
306     // the ManagerRequestId is not unique. We have to create another unique ID called
307     // ServiceRequestId and pass it to underlying layer (VehicleHal and VehicleStub).
308     // Internally, we will map ManagerRequestId to ServiceRequestId.
309     private final AtomicInteger mServiceRequestIdCounter = new AtomicInteger(0);
310     // Only contains property ID if value is different for the CarPropertyManager and the HAL.
311     private static final BidirectionalSparseIntArray MGR_PROP_ID_TO_HAL_PROP_ID =
312             BidirectionalSparseIntArray.create(
313                     new int[]{VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
314                             VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS});
315     private final VehicleHal mVehicleHal;
316     private final PropertyHalServiceIds mPropertyHalServiceIds = new PropertyHalServiceIds();
317     private final HalPropValueBuilder mPropValueBuilder;
318     private final HandlerThread mHandlerThread =
319             CarServiceUtils.getHandlerThread(getClass().getSimpleName());
320     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
321     private final TimeoutCallback mTimeoutCallback = new AsyncRequestTimeoutCallback();
322 
323     private final Object mLock = new Object();
324     @GuardedBy("mLock")
325     private final Map<IBinder, VehicleStubCallback>
326             mResultBinderToVehicleStubCallback = new ArrayMap<>();
327     @GuardedBy("mLock")
328     private final SparseArray<CarPropertyConfig<?>> mMgrPropIdToCarPropConfig = new SparseArray<>();
329     @GuardedBy("mLock")
330     private final SparseArray<HalPropConfig> mHalPropIdToPropConfig =
331             new SparseArray<>();
332     @GuardedBy("mLock")
333     private final SparseArray<Pair<String, String>> mMgrPropIdToPermissions = new SparseArray<>();
334     // A pending request pool to store all pending async get/set property request info.
335     // Service request ID is int, not long, but we only have one version of PendingRequestPool.
336     @GuardedBy("mLock")
337     private final LongPendingRequestPool<AsyncPropRequestInfo> mPendingAsyncRequests =
338             new LongPendingRequestPool<>(mHandler.getLooper(), mTimeoutCallback);
339     @GuardedBy("mLock")
340     private PropertyHalListener mPropertyHalListener;
341     // A map from subscribed PropertyHalService property IDs to their current update rate.
342     // This value will be updated by {@link #subscribeProperty} or {@link #unsubscribeProperty}.
343     @GuardedBy("mLock")
344     private final SparseArray<Float> mSubscribedHalPropIdToUpdateRateHz = new SparseArray<>();
345     // A map to store pending async set request info that are currently waiting for property update
346     // events.
347     @GuardedBy("mLock")
348     private final SparseArray<List<AsyncPropRequestInfo>> mHalPropIdToWaitingUpdateRequestInfo =
349             new SparseArray<>();
350 
351     private class AsyncRequestTimeoutCallback implements TimeoutCallback {
352         @Override
onRequestsTimeout(List<Long> serviceRequestIds)353         public void onRequestsTimeout(List<Long> serviceRequestIds) {
354             ArrayMap<VehicleStubCallback, List<Integer>> callbackToRequestIds = new ArrayMap<>();
355             synchronized (mLock) {
356                 // Get the callback for the pending requests.
357                 for (int i = 0; i < serviceRequestIds.size(); i++) {
358                     // Service ID is always a valid int.
359                     int serviceRequestId = serviceRequestIds.get(i).intValue();
360                     AsyncPropRequestInfo requestInfo =
361                             getPendingAsyncPropRequestInfoLocked(serviceRequestId);
362                     if (requestInfo == null) {
363                         Slogf.w(TAG, "The pending request: %d finished before timeout handler",
364                                 serviceRequestId);
365                         continue;
366                     }
367                     VehicleStubCallback callback = requestInfo.getVehicleStubCallback();
368                     if (callbackToRequestIds.get(callback) == null) {
369                         callbackToRequestIds.put(callback, new ArrayList<>());
370                     }
371                     callbackToRequestIds.get(callback).add(serviceRequestId);
372                 }
373             }
374             for (int i = 0; i < callbackToRequestIds.size(); i++) {
375                 callbackToRequestIds.keyAt(i).onRequestsTimeout(callbackToRequestIds.valueAt(i));
376             }
377         }
378     }
379 
380     private class VehicleStubCallback extends VehicleStubCallbackInterface {
381         private final IAsyncPropertyResultCallback mAsyncPropertyResultCallback;
382         private final IBinder mClientBinder;
383 
VehicleStubCallback( IAsyncPropertyResultCallback asyncPropertyResultCallback)384         VehicleStubCallback(
385                 IAsyncPropertyResultCallback asyncPropertyResultCallback) {
386             mAsyncPropertyResultCallback = asyncPropertyResultCallback;
387             mClientBinder = asyncPropertyResultCallback.asBinder();
388         }
389 
sendGetValueResults(List<GetSetValueResult> results)390         private void sendGetValueResults(List<GetSetValueResult> results) {
391             if (results.isEmpty()) {
392                 return;
393             }
394             try {
395                 mAsyncPropertyResultCallback.onGetValueResults(new GetSetValueResultList(results));
396             } catch (RemoteException e) {
397                 Slogf.w(TAG, "sendGetValueResults: Client might have died already", e);
398             }
399         }
400 
sendSetValueResults(List<GetSetValueResult> results)401         void sendSetValueResults(List<GetSetValueResult> results) {
402             if (results.isEmpty()) {
403                 return;
404             }
405             try {
406                 mAsyncPropertyResultCallback.onSetValueResults(new GetSetValueResultList(results));
407             } catch (RemoteException e) {
408                 Slogf.w(TAG, "sendSetValueResults: Client might have died already", e);
409             }
410         }
411 
retryIfNotExpired(List<AsyncPropRequestInfo> retryRequests)412         private void retryIfNotExpired(List<AsyncPropRequestInfo> retryRequests) {
413             List<AsyncGetSetRequest> vehicleStubAsyncGetRequests = new ArrayList<>();
414             List<GetSetValueResult> timeoutGetResults = new ArrayList<>();
415             List<AsyncGetSetRequest> vehicleStubAsyncSetRequests = new ArrayList<>();
416             List<GetSetValueResult> timeoutSetResults = new ArrayList<>();
417             List<AsyncPropRequestInfo> pendingRetryRequests = new ArrayList<>();
418             synchronized (mLock) {
419                 // Get the current time after obtaining lock since it might take some time to get
420                 // the lock.
421                 long currentUptimeMs = SystemClock.uptimeMillis();
422                 for (int i = 0; i < retryRequests.size(); i++) {
423                     AsyncPropRequestInfo requestInfo = retryRequests.get(i);
424                     long timeoutUptimeMs = requestInfo.getTimeoutUptimeMs();
425                     if (timeoutUptimeMs <= currentUptimeMs) {
426                         // The request already expired.
427                         generateTimeoutResult(requestInfo, timeoutGetResults, timeoutSetResults);
428                         continue;
429                     }
430 
431                     // Generate a new service request ID and async request object for the retry.
432                     AsyncGetSetRequest vehicleStubAsyncRequest =
433                             generateVehicleStubAsyncRequestLocked(requestInfo);
434                     pendingRetryRequests.add(requestInfo);
435 
436                     switch (requestInfo.getRequestType()) {
437                         case GET: // fallthrough
438                         case GET_INITIAL_VALUE_FOR_SET:
439                             vehicleStubAsyncGetRequests.add(vehicleStubAsyncRequest);
440                             break;
441                         case SET:
442                             vehicleStubAsyncSetRequests.add(vehicleStubAsyncRequest);
443                             break;
444                     }
445                 }
446 
447                 // We already marked all the input requests as finished. Now for the new retry
448                 // requests, we need to put them back into the pending request pool.
449                 mPendingAsyncRequests.addPendingRequests(pendingRetryRequests);
450             }
451 
452             sendGetValueResults(timeoutGetResults);
453             if (!vehicleStubAsyncGetRequests.isEmpty()) {
454                 mVehicleHal.getAsync(vehicleStubAsyncGetRequests, this);
455             }
456             sendSetValueResults(timeoutSetResults);
457             if (!vehicleStubAsyncSetRequests.isEmpty()) {
458                 mVehicleHal.setAsync(vehicleStubAsyncSetRequests, this);
459             }
460         }
461 
getClientBinder()462         IBinder getClientBinder() {
463             return mClientBinder;
464         }
465 
466         // This is a wrapper for death recipient that will unlink itself upon binder death.
467         private final class DeathRecipientWrapper implements DeathRecipient {
468             private DeathRecipient mInnerRecipient;
469 
DeathRecipientWrapper(DeathRecipient innerRecipient)470             DeathRecipientWrapper(DeathRecipient innerRecipient) {
471                 mInnerRecipient = innerRecipient;
472             }
473 
474             @Override
binderDied()475             public void binderDied() {
476                 mInnerRecipient.binderDied();
477                 mClientBinder.unlinkToDeath(this, /* flags= */ 0);
478             }
479         }
480 
481         @Override
linkToDeath(DeathRecipient recipient)482         public void linkToDeath(DeathRecipient recipient) throws RemoteException {
483             mClientBinder.linkToDeath(new DeathRecipientWrapper(recipient),
484                     /* flags= */ 0);
485         }
486 
487         // Parses an async getProperty result and convert it to an okay/error result.
parseGetAsyncResults( GetVehicleStubAsyncResult getVehicleStubAsyncResult, AsyncPropRequestInfo clientRequestInfo)488         private GetSetValueResult parseGetAsyncResults(
489                 GetVehicleStubAsyncResult getVehicleStubAsyncResult,
490                 AsyncPropRequestInfo clientRequestInfo) {
491             int carPropMgrErrorCode = getVehicleStubAsyncResult.getErrorCode();
492             if (carPropMgrErrorCode != STATUS_OK) {
493                 // All other error results will be delivered back through callback.
494                 return clientRequestInfo.toErrorResult(carPropMgrErrorCode,
495                         getVehicleStubAsyncResult.getVendorErrorCode());
496             }
497 
498             // For okay status, convert the property value to the type the client expects.
499             int mgrPropId = clientRequestInfo.getPropertyId();
500             int halPropId = managerToHalPropId(mgrPropId);
501             HalPropConfig halPropConfig;
502             synchronized (mLock) {
503                 halPropConfig = mHalPropIdToPropConfig.get(halPropId);
504             }
505             if (halPropConfig == null) {
506                 Slogf.e(TAG, "No configuration found for property: %s, must not happen",
507                         clientRequestInfo.getPropertyName());
508                 return clientRequestInfo.toErrorResult(
509                         CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR,
510                         /* vendorErrorCode= */ 0);
511             }
512             HalPropValue halPropValue = getVehicleStubAsyncResult.getHalPropValue();
513             if (halPropValue.getStatus() == VehiclePropertyStatus.UNAVAILABLE) {
514                 return clientRequestInfo.toErrorResult(
515                         CarPropertyManager.STATUS_ERROR_NOT_AVAILABLE,
516                         /* vendorErrorCode= */ 0);
517             }
518             if (halPropValue.getStatus() != VehiclePropertyStatus.AVAILABLE) {
519                 return clientRequestInfo.toErrorResult(
520                         CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR,
521                         /* vendorErrorCode= */ 0);
522             }
523 
524             try {
525                 return clientRequestInfo.toGetValueResult(
526                         halPropValue.toCarPropertyValue(mgrPropId, halPropConfig));
527             } catch (IllegalStateException e) {
528                 Slogf.e(TAG, e,
529                         "Cannot convert halPropValue to carPropertyValue, property: %s, areaId: %d",
530                         halPropIdToName(halPropValue.getPropId()), halPropValue.getAreaId());
531                 return clientRequestInfo.toErrorResult(
532                         CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR,
533                         /* vendorErrorCode= */ 0);
534             }
535         }
536 
537         @Override
onGetAsyncResults( List<GetVehicleStubAsyncResult> getVehicleStubAsyncResults)538         public void onGetAsyncResults(
539                 List<GetVehicleStubAsyncResult> getVehicleStubAsyncResults) {
540             List<GetSetValueResult> getValueResults = new ArrayList<>();
541             // If we receive get value result for initial value request and the result is the
542             // same as the target value, we might finish the associated async set value request.
543             // So we need potential set value results here.
544             List<GetSetValueResult> setValueResults = new ArrayList<>();
545             List<AsyncPropRequestInfo> retryRequests = new ArrayList<>();
546             synchronized (mLock) {
547                 Set<Integer> updatedHalPropIds = new ArraySet<>();
548                 for (int i = 0; i < getVehicleStubAsyncResults.size(); i++) {
549                     GetVehicleStubAsyncResult getVehicleStubAsyncResult =
550                             getVehicleStubAsyncResults.get(i);
551                     int serviceRequestId = getVehicleStubAsyncResult.getServiceRequestId();
552                     AsyncPropRequestInfo clientRequestInfo =
553                             getAndRemovePendingAsyncPropRequestInfoLocked(serviceRequestId,
554                                     updatedHalPropIds);
555                     if (clientRequestInfo == null) {
556                         Slogf.w(TAG, "async request for ID: %d not found, ignore the result",
557                                 serviceRequestId);
558                         continue;
559                     }
560 
561                     int carPropMgrErrorCode = getVehicleStubAsyncResult.getErrorCode();
562                     if (carPropMgrErrorCode == VehicleStub.STATUS_TRY_AGAIN) {
563                         // The request might need to be retried.
564                         if (DBG) {
565                             Slogf.d(TAG, "request: %s try again", clientRequestInfo);
566                         }
567                         retryRequests.add(clientRequestInfo);
568                         continue;
569                     }
570 
571                     GetSetValueResult result = parseGetAsyncResults(getVehicleStubAsyncResult,
572                             clientRequestInfo);
573                     if (clientRequestInfo.getRequestType() != GET_INITIAL_VALUE_FOR_SET) {
574                         getValueResults.add(result);
575                         continue;
576                     }
577 
578                     if (DBG) {
579                         Slogf.d(TAG, "handling init value result for request: %s",
580                                 clientRequestInfo);
581                     }
582                     // Handle GET_INITIAL_VALUE_FOR_SET result.
583                     int errorCode = result.getErrorCode();
584                     if (errorCode != STATUS_OK) {
585                         Slogf.w(TAG, "the init value get request: %s failed, ignore the result, "
586                                 + "error: %d", clientRequestInfo, errorCode);
587                         continue;
588                     }
589                     // If the initial value result is the target value and the async set
590                     // request returned, we finish the pending async set result.
591                     AsyncPropRequestInfo assocSetValueRequestInfo =
592                             clientRequestInfo.getAssocSetValueRequestInfo();
593                     if (assocSetValueRequestInfo == null) {
594                         Slogf.e(TAG, "received get initial value result, but no associated set "
595                                 + "value request is defined");
596                         continue;
597                     }
598                     GetSetValueResult maybeSetResult = maybeFinishPendingSetValueRequestLocked(
599                             assocSetValueRequestInfo, result.getCarPropertyValue());
600                     if (maybeSetResult != null) {
601                         if (DBG) {
602                             Slogf.d(TAG, "The initial value is the same as target value for "
603                                     + "request: %s, sending success set result",
604                                     assocSetValueRequestInfo);
605                         }
606                         setValueResults.add(maybeSetResult);
607                         removePendingAsyncPropRequestInfoLocked(
608                                 assocSetValueRequestInfo, updatedHalPropIds);
609                     }
610                 }
611                 updateSubscriptionRateLocked(updatedHalPropIds);
612             }
613 
614             sendGetValueResults(getValueResults);
615             sendSetValueResults(setValueResults);
616 
617             if (!retryRequests.isEmpty()) {
618                 mHandler.postDelayed(() -> {
619                     retryIfNotExpired(retryRequests);
620                 }, ASYNC_RETRY_SLEEP_IN_MS);
621             }
622         }
623 
624         @Override
onSetAsyncResults( List<SetVehicleStubAsyncResult> setVehicleStubAsyncResults)625         public void onSetAsyncResults(
626                 List<SetVehicleStubAsyncResult> setVehicleStubAsyncResults) {
627             List<GetSetValueResult> setValueResults = new ArrayList<>();
628             List<AsyncPropRequestInfo> retryRequests = new ArrayList<>();
629             Set<Integer> updatedHalPropIds = new ArraySet<>();
630             synchronized (mLock) {
631                 for (int i = 0; i < setVehicleStubAsyncResults.size(); i++) {
632                     SetVehicleStubAsyncResult setVehicleStubAsyncResult =
633                             setVehicleStubAsyncResults.get(i);
634                     int serviceRequestId = setVehicleStubAsyncResult.getServiceRequestId();
635                     AsyncPropRequestInfo clientRequestInfo =
636                             getPendingAsyncPropRequestInfoLocked(serviceRequestId);
637                     if (clientRequestInfo == null) {
638                         Slogf.w(TAG, "async request for ID:  %d not found, ignore the result",
639                                 serviceRequestId);
640                         continue;
641                     }
642                     int carPropMgrErrorCode = setVehicleStubAsyncResult.getErrorCode();
643 
644                     if (carPropMgrErrorCode == VehicleStub.STATUS_TRY_AGAIN) {
645                         // The request might need to be retried.
646                         retryRequests.add(clientRequestInfo);
647                         removePendingAsyncPropRequestInfoLocked(clientRequestInfo,
648                                 updatedHalPropIds);
649                         continue;
650                     }
651 
652                     if (carPropMgrErrorCode != STATUS_OK) {
653                         // All other error results will be delivered back through callback.
654                         setValueResults.add(clientRequestInfo.toErrorResult(
655                                 carPropMgrErrorCode,
656                                 setVehicleStubAsyncResult.getVendorErrorCode()));
657                         removePendingAsyncPropRequestInfoLocked(clientRequestInfo,
658                                 updatedHalPropIds);
659                         continue;
660                     }
661 
662                     clientRequestInfo.setSetRequestSent();
663                     if (clientRequestInfo.success()) {
664                         // If we have already received event for the target value or the initial
665                         // value is already the target value. Mark the request as complete.
666                         removePendingAsyncPropRequestInfoLocked(clientRequestInfo,
667                                 updatedHalPropIds);
668                          // If we don't wait for property update event, then we don't know when
669                         // the property is updated to the target value. We set it to the
670                         // current timestamp.
671                         long updateTimestampNanos = clientRequestInfo.isWaitForPropertyUpdate()
672                                 ? clientRequestInfo.getUpdateTimestampNanos() :
673                                 SystemClock.elapsedRealtimeNanos();
674                         setValueResults.add(clientRequestInfo.toSetValueResult(
675                                 updateTimestampNanos));
676                     }
677                 }
678                 updateSubscriptionRateLocked(updatedHalPropIds);
679             }
680 
681             sendSetValueResults(setValueResults);
682 
683             if (!retryRequests.isEmpty()) {
684                 mHandler.postDelayed(() -> {
685                     retryIfNotExpired(retryRequests);
686                 }, ASYNC_RETRY_SLEEP_IN_MS);
687             }
688         }
689 
generateTimeoutResult(AsyncPropRequestInfo requestInfo, List<GetSetValueResult> timeoutGetResults, List<GetSetValueResult> timeoutSetResults)690         private void generateTimeoutResult(AsyncPropRequestInfo requestInfo,
691                 List<GetSetValueResult> timeoutGetResults,
692                 List<GetSetValueResult> timeoutSetResults) {
693             GetSetValueResult timeoutResult =  requestInfo.toErrorResult(
694                     CarPropertyManager.STATUS_ERROR_TIMEOUT,
695                     /* vendorErrorCode= */ 0);
696             switch (requestInfo.getRequestType()) {
697                 case GET:
698                     timeoutGetResults.add(timeoutResult);
699                     break;
700                 case GET_INITIAL_VALUE_FOR_SET:
701                     // Do not send the timeout requests back to the user because the original
702                     // request is not originated from the user.
703                     Slogf.e(TAG, "the initial value request: %s timeout", requestInfo);
704                     break;
705                 case SET:
706                     timeoutSetResults.add(timeoutResult);
707                     break;
708             }
709         }
710 
711         @Override
onRequestsTimeout(List<Integer> serviceRequestIds)712         public void onRequestsTimeout(List<Integer> serviceRequestIds) {
713             List<GetSetValueResult> timeoutGetResults = new ArrayList<>();
714             List<GetSetValueResult> timeoutSetResults = new ArrayList<>();
715             Set<Integer> updatedHalPropIds = new ArraySet<>();
716             synchronized (mLock) {
717                 for (int i = 0; i < serviceRequestIds.size(); i++) {
718                     int serviceRequestId = serviceRequestIds.get(i);
719                     AsyncPropRequestInfo requestInfo =
720                             getAndRemovePendingAsyncPropRequestInfoLocked(serviceRequestId,
721                                     updatedHalPropIds);
722                     if (requestInfo == null) {
723                         Slogf.w(TAG, "Service request ID %d time out but no "
724                                 + "pending request is found. The request may have already been "
725                                 + "cancelled or finished", serviceRequestId);
726                         continue;
727                     }
728                     if (DBG) {
729                         Slogf.d(TAG, "Request: %s time out", requestInfo);
730                     }
731                     generateTimeoutResult(requestInfo, timeoutGetResults, timeoutSetResults);
732                 }
733                 updateSubscriptionRateLocked(updatedHalPropIds);
734             }
735             sendGetValueResults(timeoutGetResults);
736             sendSetValueResults(timeoutSetResults);
737         }
738     }
739 
740     /**
741      * Converts manager property ID to Vehicle HAL property ID.
742      */
managerToHalPropId(int mgrPropId)743     private static int managerToHalPropId(int mgrPropId) {
744         return MGR_PROP_ID_TO_HAL_PROP_ID.getValue(mgrPropId, mgrPropId);
745     }
746 
747     /**
748      * Converts Vehicle HAL property ID to manager property ID.
749      */
halToManagerPropId(int halPropId)750     private static int halToManagerPropId(int halPropId) {
751         return MGR_PROP_ID_TO_HAL_PROP_ID.getKey(halPropId, halPropId);
752     }
753 
754     /**
755      * Maybe finish the pending set value request depending on the updated value.
756      *
757      * Check whether the updated property value is the same as the target value for pending
758      * set value requests. If so, finish those requests.
759      *
760      * @return A success set value result for the finished request or {@code null}.
761      */
762     @GuardedBy("mLock")
763     @Nullable
maybeFinishPendingSetValueRequestLocked( AsyncPropRequestInfo pendingSetValueRequest, CarPropertyValue updatedValue)764     private GetSetValueResult maybeFinishPendingSetValueRequestLocked(
765             AsyncPropRequestInfo pendingSetValueRequest, CarPropertyValue updatedValue) {
766         Object targetValue = pendingSetValueRequest.getPropSvcRequest()
767                 .getCarPropertyValue().getValue();
768         Object currentValue = updatedValue.getValue();
769         if (!targetValue.equals(currentValue)) {
770             if (DBG) {
771                 Slogf.d(TAG, "Async set value request: %s receive different updated value: %s"
772                         + " than target value: %s", pendingSetValueRequest, currentValue,
773                         targetValue);
774             }
775             return null;
776         }
777         long updateTimestampNanos = updatedValue.getTimestamp();
778         pendingSetValueRequest.setValueUpdated(updateTimestampNanos);
779         if (!pendingSetValueRequest.success()) {
780             return null;
781         }
782 
783         return pendingSetValueRequest.toSetValueResult(updateTimestampNanos);
784     }
785 
786     /**
787      * Generates a {@link AsyncGetSetRequest} according to a {@link AsyncPropRequestInfo}.
788      *
789      * <p>Generates a new PropertyHalService Request ID. Associate the ID with the request and
790      * returns a {@link AsyncGetSetRequest} that could be sent to {@link VehicleStub}.
791      */
792     @GuardedBy("mLock")
generateVehicleStubAsyncRequestLocked( AsyncPropRequestInfo asyncPropRequestInfo)793     private AsyncGetSetRequest generateVehicleStubAsyncRequestLocked(
794             AsyncPropRequestInfo asyncPropRequestInfo) {
795         int serviceRequestId = mServiceRequestIdCounter.getAndIncrement();
796         asyncPropRequestInfo.setServiceRequestId(serviceRequestId);
797 
798         HalPropValue halPropValue;
799         CarPropertyValue requestCarPropertyValue = asyncPropRequestInfo.getPropSvcRequest()
800                 .getCarPropertyValue();
801         if (requestCarPropertyValue != null) {
802             // If this is a set request, the car property value stores the value to be set.
803             halPropValue = carPropertyValueToHalPropValueLocked(requestCarPropertyValue);
804         } else {
805             // Otherwise this is a get request, we only need the property ID and area ID.
806             int halPropertyId = managerToHalPropId(asyncPropRequestInfo.getPropertyId());
807             int areaId = asyncPropRequestInfo.getAreaId();
808             halPropValue = mPropValueBuilder.build(halPropertyId, areaId);
809         }
810         return new AsyncGetSetRequest(serviceRequestId, halPropValue,
811                 asyncPropRequestInfo.getTimeoutUptimeMs());
812     }
813 
814     @GuardedBy("mLock")
getPendingAsyncPropRequestInfoLocked( int serviceRequestId)815     @Nullable private AsyncPropRequestInfo getPendingAsyncPropRequestInfoLocked(
816             int serviceRequestId) {
817         AsyncPropRequestInfo requestInfo =
818                 mPendingAsyncRequests.getRequestIfFound(serviceRequestId);
819         if (requestInfo == null) {
820             Slogf.w(TAG, "the request for propertyHalService request "
821                     + "ID: %d already timed out or already completed", serviceRequestId);
822         }
823         return requestInfo;
824     }
825 
826     @GuardedBy("mLock")
getAndRemovePendingAsyncPropRequestInfoLocked( int serviceRequestId, Set<Integer> updatedHalPropIds)827     @Nullable private AsyncPropRequestInfo getAndRemovePendingAsyncPropRequestInfoLocked(
828             int serviceRequestId, Set<Integer> updatedHalPropIds) {
829         AsyncPropRequestInfo requestInfo = getPendingAsyncPropRequestInfoLocked(serviceRequestId);
830         if (requestInfo == null) {
831             Slogf.w(TAG, "the request for propertyHalService request "
832                     + "ID: %d already timed out or already completed", serviceRequestId);
833             return null;
834         }
835         removePendingAsyncPropRequestInfoLocked(requestInfo, updatedHalPropIds);
836         return requestInfo;
837     }
838 
839     /**
840      * Remove the pending async request from the pool.
841      *
842      * If the request to remove is an async set request, also remove it from the
843      * {@code mHalPropIdToWaitingUpdateRequestInfo} map. This will cause the subscription rate to
844      * be updated for the specific property because we no longer need to monitor this property
845      * any more internally.
846      *
847      * The {@code updatedHalPropIds} will store the affected property IDs if their subscription
848      * rate need to be recalculated.
849      */
850     @GuardedBy("mLock")
removePendingAsyncPropRequestInfoLocked( AsyncPropRequestInfo pendingRequest, Set<Integer> updatedHalPropIds)851     private void removePendingAsyncPropRequestInfoLocked(
852             AsyncPropRequestInfo pendingRequest, Set<Integer> updatedHalPropIds) {
853         int serviceRequestId = pendingRequest.getServiceRequestId();
854         mPendingAsyncRequests.removeRequest(serviceRequestId);
855         if (pendingRequest.getRequestType() == SET) {
856             cleanupPendingAsyncSetRequestLocked(pendingRequest, updatedHalPropIds);
857         }
858     }
859 
860     @GuardedBy("mLock")
cleanupPendingAsyncSetRequestLocked( AsyncPropRequestInfo pendingRequest, Set<Integer> updatedHalPropIds)861     private void cleanupPendingAsyncSetRequestLocked(
862             AsyncPropRequestInfo pendingRequest, Set<Integer> updatedHalPropIds) {
863         int halPropId = managerToHalPropId(pendingRequest.getPropertyId());
864         if (!pendingRequest.isWaitForPropertyUpdate()) {
865             return;
866         }
867         if (pendingRequest.getAssocGetInitValueRequestInfo() == null) {
868             Slogf.e(TAG, "The pending async set value request: %s"
869                     + " does not have an associated get initial value request, must not happen",
870                     pendingRequest);
871             return;
872         }
873         // If we are removing an async set property request, then we should remove its associated
874         // get initial value request as well if it has not been finished.
875         AsyncPropRequestInfo assocGetInitValueRequestInfo =
876                 pendingRequest.getAssocGetInitValueRequestInfo();
877         int assocInitValueRequestId = assocGetInitValueRequestInfo.getServiceRequestId();
878         assocGetInitValueRequestInfo = mPendingAsyncRequests.getRequestIfFound(
879                 assocInitValueRequestId);
880         if (assocGetInitValueRequestInfo != null) {
881             mPendingAsyncRequests.removeRequest(assocInitValueRequestId);
882             // Use a separate runnable to do this outside lock.
883             mHandler.post(() -> mVehicleHal.cancelRequests(List.of(assocInitValueRequestId)));
884         }
885         if (!mHalPropIdToWaitingUpdateRequestInfo.contains(halPropId)) {
886             return;
887         }
888         if (!mHalPropIdToWaitingUpdateRequestInfo.get(halPropId).remove(pendingRequest)) {
889             return;
890         }
891         if (mHalPropIdToWaitingUpdateRequestInfo.get(halPropId).isEmpty()) {
892             mHalPropIdToWaitingUpdateRequestInfo.remove(halPropId);
893         }
894         updatedHalPropIds.add(halPropId);
895     }
896 
897     /**
898      * PropertyHalListener used to send events to CarPropertyService
899      */
900     public interface PropertyHalListener {
901         /**
902          * This event is sent whenever the property value is updated
903          */
onPropertyChange(List<CarPropertyEvent> events)904         void onPropertyChange(List<CarPropertyEvent> events);
905 
906         /**
907          * This event is sent when the set property call fails
908          */
onPropertySetError(int property, int area, @CarSetPropertyErrorCode int errorCode)909         void onPropertySetError(int property, int area,
910                 @CarSetPropertyErrorCode int errorCode);
911 
912     }
913 
PropertyHalService(VehicleHal vehicleHal)914     public PropertyHalService(VehicleHal vehicleHal) {
915         mVehicleHal = vehicleHal;
916         if (DBG) {
917             Slogf.d(TAG, "started PropertyHalService");
918         }
919         mPropValueBuilder = vehicleHal.getHalPropValueBuilder();
920     }
921 
922     /**
923      * Set the listener for the HAL service
924      */
setPropertyHalListener(PropertyHalListener propertyHalListener)925     public void setPropertyHalListener(PropertyHalListener propertyHalListener) {
926         synchronized (mLock) {
927             mPropertyHalListener = propertyHalListener;
928         }
929     }
930 
931     /**
932      * @return SparseArray<CarPropertyConfig> List of configs available.
933      */
getPropertyList()934     public SparseArray<CarPropertyConfig<?>> getPropertyList() {
935         if (DBG) {
936             Slogf.d(TAG, "getPropertyList");
937         }
938         synchronized (mLock) {
939             if (mMgrPropIdToCarPropConfig.size() == 0) {
940                 for (int i = 0; i < mHalPropIdToPropConfig.size(); i++) {
941                     HalPropConfig halPropConfig = mHalPropIdToPropConfig.valueAt(i);
942                     int mgrPropId = halToManagerPropId(halPropConfig.getPropId());
943                     CarPropertyConfig<?> carPropertyConfig = halPropConfig.toCarPropertyConfig(
944                             mgrPropId);
945                     mMgrPropIdToCarPropConfig.put(mgrPropId, carPropertyConfig);
946                 }
947             }
948             return mMgrPropIdToCarPropConfig;
949         }
950     }
951 
952     /**
953      * Returns property value.
954      *
955      * @param mgrPropId property id in {@link VehiclePropertyIds}
956      * @throws IllegalArgumentException if argument is not valid.
957      * @throws ServiceSpecificException if there is an exception in HAL or the property status is
958      *                                  not available.
959      */
getProperty(int mgrPropId, int areaId)960     public CarPropertyValue getProperty(int mgrPropId, int areaId)
961             throws IllegalArgumentException, ServiceSpecificException {
962         int halPropId = managerToHalPropId(mgrPropId);
963         // CarPropertyManager catches and rethrows exception, no need to handle here.
964         HalPropValue halPropValue = mVehicleHal.get(halPropId, areaId);
965         HalPropConfig halPropConfig;
966         synchronized (mLock) {
967             halPropConfig = mHalPropIdToPropConfig.get(halPropId);
968         }
969         halPropValue = mVehicleHal.get(halPropId, areaId);
970         try {
971             return halPropValue.toCarPropertyValue(mgrPropId, halPropConfig);
972         } catch (IllegalStateException e) {
973             throw new ServiceSpecificException(STATUS_INTERNAL_ERROR,
974                     "Cannot convert halPropValue to carPropertyValue, property: "
975                     + VehiclePropertyIds.toString(mgrPropId) + " areaId: " + areaId
976                     + ", exception: " + e);
977         }
978     }
979 
980     /**
981      * Returns update rate in HZ for the subscribed property, or -1 if not subscribed.
982      *
983      * The update rate returned here only consideres the subscription originated from
984      * {@link PropertyHalService#subscribeProperty} and does not consider the internal subscription
985      * for async set value requests.
986      */
getSubscribedUpdateRateHz(int mgrPropId)987     public float getSubscribedUpdateRateHz(int mgrPropId) {
988         int halPropId = managerToHalPropId(mgrPropId);
989         synchronized (mLock) {
990             return mSubscribedHalPropIdToUpdateRateHz.get(halPropId, Float.valueOf(-1f));
991         }
992     }
993 
994     /**
995      * Get the read permission string for the property.
996      */
997     @Nullable
getReadPermission(int mgrPropId)998     public String getReadPermission(int mgrPropId) {
999         int halPropId = managerToHalPropId(mgrPropId);
1000         return mPropertyHalServiceIds.getReadPermission(halPropId);
1001     }
1002 
1003     /**
1004      * Get the write permission string for the property.
1005      */
1006     @Nullable
getWritePermission(int mgrPropId)1007     public String getWritePermission(int mgrPropId) {
1008         int halPropId = managerToHalPropId(mgrPropId);
1009         return mPropertyHalServiceIds.getWritePermission(halPropId);
1010     }
1011 
1012     /**
1013      * Get permissions for all properties in the vehicle.
1014      *
1015      * @return a SparseArray. key: propertyId, value: Pair(readPermission, writePermission).
1016      */
1017     @NonNull
getPermissionsForAllProperties()1018     public SparseArray<Pair<String, String>> getPermissionsForAllProperties() {
1019         synchronized (mLock) {
1020             if (mMgrPropIdToPermissions.size() != 0) {
1021                 return mMgrPropIdToPermissions;
1022             }
1023             for (int i = 0; i < mHalPropIdToPropConfig.size(); i++) {
1024                 int halPropId = mHalPropIdToPropConfig.keyAt(i);
1025                 mMgrPropIdToPermissions.put(halToManagerPropId(halPropId),
1026                         new Pair<>(mPropertyHalServiceIds.getReadPermission(halPropId),
1027                                 mPropertyHalServiceIds.getWritePermission(halPropId)));
1028             }
1029             return mMgrPropIdToPermissions;
1030         }
1031     }
1032 
1033     /**
1034      * Return true if property is a display_units property
1035      */
isDisplayUnitsProperty(int mgrPropId)1036     public boolean isDisplayUnitsProperty(int mgrPropId) {
1037         int halPropId = managerToHalPropId(mgrPropId);
1038         return mPropertyHalServiceIds.isPropertyToChangeUnits(halPropId);
1039     }
1040 
1041     /**
1042      * Set the property value.
1043      *
1044      * @throws IllegalArgumentException if argument is invalid.
1045      * @throws ServiceSpecificException if there is an exception in HAL.
1046      */
setProperty(CarPropertyValue carPropertyValue)1047     public void setProperty(CarPropertyValue carPropertyValue)
1048             throws IllegalArgumentException, ServiceSpecificException {
1049         HalPropValue valueToSet;
1050         synchronized (mLock) {
1051             valueToSet = carPropertyValueToHalPropValueLocked(carPropertyValue);
1052         }
1053 
1054         // CarPropertyManager catches and rethrows exception, no need to handle here.
1055         mVehicleHal.set(valueToSet);
1056     }
1057 
1058     /**
1059      * Subscribe to this property at the specified update updateRateHz.
1060      *
1061      * @throws IllegalArgumentException thrown if property is not supported by VHAL.
1062      */
subscribeProperty(int mgrPropId, float updateRateHz)1063     public void subscribeProperty(int mgrPropId, float updateRateHz)
1064             throws IllegalArgumentException {
1065         if (DBG) {
1066             Slogf.d(TAG, "subscribeProperty propertyId: %s, updateRateHz=%f",
1067                     VehiclePropertyIds.toString(mgrPropId), updateRateHz);
1068         }
1069         int halPropId = managerToHalPropId(mgrPropId);
1070         synchronized (mLock) {
1071             // Even though this involves binder call, this must be done inside the lock so that
1072             // the state in {@code mSubscribedHalPropIdToUpdateRateHz} is consistent with the
1073             // state in VHAL.
1074             mSubscribedHalPropIdToUpdateRateHz.put(halPropId, updateRateHz);
1075             updateSubscriptionRateForHalPropIdLocked(halPropId);
1076         }
1077     }
1078 
1079     /**
1080      * Unsubscribe the property and turn off update events for it.
1081      */
unsubscribeProperty(int mgrPropId)1082     public void unsubscribeProperty(int mgrPropId) {
1083         if (DBG) {
1084             Slogf.d(TAG, "unsubscribeProperty mgrPropId=%s",
1085                     VehiclePropertyIds.toString(mgrPropId));
1086         }
1087         int halPropId = managerToHalPropId(mgrPropId);
1088         synchronized (mLock) {
1089             // Even though this involves binder call, this must be done inside the lock so that
1090             // the state in {@code mSubscribedHalPropIdToUpdateRateHz} is consistent with the
1091             // state in VHAL.
1092             if (mSubscribedHalPropIdToUpdateRateHz.get(halPropId) == null) {
1093                 Slogf.w(TAG, "property: %s is not subscribed.",
1094                         VehiclePropertyIds.toString(mgrPropId));
1095                 return;
1096             }
1097             mSubscribedHalPropIdToUpdateRateHz.remove(halPropId);
1098             updateSubscriptionRateForHalPropIdLocked(halPropId);
1099         }
1100     }
1101 
1102     @Override
init()1103     public void init() {
1104         if (DBG) {
1105             Slogf.d(TAG, "init()");
1106         }
1107     }
1108 
1109     @Override
release()1110     public void release() {
1111         if (DBG) {
1112             Slogf.d(TAG, "release()");
1113         }
1114         synchronized (mLock) {
1115             for (int i = 0; i < mSubscribedHalPropIdToUpdateRateHz.size(); i++) {
1116                 int halPropId = mSubscribedHalPropIdToUpdateRateHz.keyAt(i);
1117                 mVehicleHal.unsubscribeProperty(this, halPropId);
1118             }
1119             mSubscribedHalPropIdToUpdateRateHz.clear();
1120             mHalPropIdToPropConfig.clear();
1121             mMgrPropIdToCarPropConfig.clear();
1122             mMgrPropIdToPermissions.clear();
1123             mPropertyHalListener = null;
1124         }
1125         mHandlerThread.quitSafely();
1126     }
1127 
1128     @Override
isSupportedProperty(int halPropId)1129     public boolean isSupportedProperty(int halPropId) {
1130         return mPropertyHalServiceIds.isSupportedProperty(halPropId)
1131                 && CarPropertyHelper.isSupported(halToManagerPropId(halPropId));
1132     }
1133 
1134     @Override
getAllSupportedProperties()1135     public int[] getAllSupportedProperties() {
1136         return CarServiceUtils.EMPTY_INT_ARRAY;
1137     }
1138 
1139     // The method is called in HAL init(). Avoid handling complex things in here.
1140     @Override
takeProperties(Collection<HalPropConfig> halPropConfigs)1141     public void takeProperties(Collection<HalPropConfig> halPropConfigs) {
1142         for (HalPropConfig halPropConfig : halPropConfigs) {
1143             int halPropId = halPropConfig.getPropId();
1144             if (isSupportedProperty(halPropId)) {
1145                 synchronized (mLock) {
1146                     mHalPropIdToPropConfig.put(halPropId, halPropConfig);
1147                 }
1148                 if (DBG) {
1149                     Slogf.d(TAG, "takeSupportedProperties: %s", halPropIdToName(halPropId));
1150                 }
1151             } else {
1152                 if (DBG) {
1153                     Slogf.d(TAG, "takeProperties: Property: %s is not supported, ignore",
1154                             halPropIdToName(halPropId));
1155                 }
1156             }
1157         }
1158         if (DBG) {
1159             Slogf.d(TAG, "takeSupportedProperties() took %d properties", halPropConfigs.size());
1160         }
1161         // If vehicle hal support to select permission for vendor properties.
1162         HalPropConfig customizePermission = mVehicleHal.getPropConfig(
1163                 VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION);
1164         if (customizePermission != null) {
1165             mPropertyHalServiceIds.customizeVendorPermission(customizePermission.getConfigArray());
1166         } else {
1167             if (DBG) {
1168                 Slogf.d(TAG, "No custom vendor permission defined in VHAL");
1169             }
1170         }
1171     }
1172 
storeResultForRequest(GetSetValueResult result, AsyncPropRequestInfo request, Map<VehicleStubCallback, List<GetSetValueResult>> callbackToResults)1173     private static void storeResultForRequest(GetSetValueResult result,
1174             AsyncPropRequestInfo request,
1175             Map<VehicleStubCallback, List<GetSetValueResult>> callbackToResults) {
1176         VehicleStubCallback clientCallback = request.getVehicleStubCallback();
1177         if (callbackToResults.get(clientCallback) == null) {
1178             callbackToResults.put(clientCallback, new ArrayList<>());
1179         }
1180         callbackToResults.get(clientCallback).add(result);
1181     }
1182 
1183     /**
1184      * Check whether there is pending async set value request for the property.
1185      *
1186      * If there are pending async set value request, check whether the updated property value is
1187      * the target value. If so, store the success set value result into callbackToSetValueResults.
1188      */
1189     @GuardedBy("mLock")
checkPendingWaitForUpdateRequestsLocked(int halPropId, CarPropertyValue<?> updatedValue, Map<VehicleStubCallback, List<GetSetValueResult>> callbackToSetValueResults, Set<Integer> updatedHalPropIds)1190     private void checkPendingWaitForUpdateRequestsLocked(int halPropId,
1191             CarPropertyValue<?> updatedValue,
1192             Map<VehicleStubCallback, List<GetSetValueResult>> callbackToSetValueResults,
1193             Set<Integer> updatedHalPropIds) {
1194         List<AsyncPropRequestInfo> pendingSetRequests = mHalPropIdToWaitingUpdateRequestInfo.get(
1195                 halPropId);
1196         if (pendingSetRequests == null) {
1197             return;
1198         }
1199         List<AsyncPropRequestInfo> finishedPendingSetRequests = new ArrayList<>();
1200         for (AsyncPropRequestInfo pendingSetRequest : pendingSetRequests) {
1201             GetSetValueResult maybeSetResult = maybeFinishPendingSetValueRequestLocked(
1202                     pendingSetRequest, updatedValue);
1203             if (pendingSetRequest.getAreaId() != updatedValue.getAreaId()) {
1204                 continue;
1205             }
1206             // Don't remove the finished pending request info during the loop since it will
1207             // modify pendingSetRequests array.
1208             if (maybeSetResult == null) {
1209                 if (DBG) {
1210                     Slogf.d(TAG, "received property update event for request: %s, but the value is "
1211                             + "different than target value", pendingSetRequest);
1212                 }
1213                 continue;
1214             }
1215             if (DBG) {
1216                 Slogf.d(TAG, "received property update to target value event for request: %s"
1217                         + ", sending success async set value result", pendingSetRequest);
1218             }
1219             storeResultForRequest(maybeSetResult, pendingSetRequest, callbackToSetValueResults);
1220             finishedPendingSetRequests.add(pendingSetRequest);
1221         }
1222 
1223         for (AsyncPropRequestInfo finishedRequest : finishedPendingSetRequests) {
1224             // Pending set value request is now succeeded. Remove all record to the pending request.
1225             removePendingAsyncPropRequestInfoLocked(finishedRequest, updatedHalPropIds);
1226         }
1227     }
1228 
1229     /**
1230      * Calculate the new subscription rate for the hal property ID.
1231      *
1232      * Use {@code subscribeProperty} to update its subscription rate or {@code unsubscribeProperty}
1233      * if it is no longer subscribed.
1234      *
1235      * Note that {@code VehicleHal} subscription logic will ignore subscribe property request with
1236      * the same subscription rate, so we do not need to check that here.
1237      */
1238     @GuardedBy("mLock")
updateSubscriptionRateForHalPropIdLocked(int halPropId)1239     private void updateSubscriptionRateForHalPropIdLocked(int halPropId) {
1240         Float newUpdateRateHz = calcNewUpdateRateHzLocked(halPropId);
1241         String propertyName = halPropIdToName(halPropId);
1242         if (newUpdateRateHz == null) {
1243             if (DBG) {
1244                 Slogf.d(TAG, "unsubscribeProperty for property ID: %s", propertyName);
1245             }
1246             mVehicleHal.unsubscribeProperty(this, halPropId);
1247         } else {
1248             if (DBG) {
1249                 Slogf.d(TAG, "subscribeProperty for property ID: %s, new sample rate: %f hz",
1250                         propertyName, newUpdateRateHz);
1251             }
1252             mVehicleHal.subscribeProperty(this, halPropId, newUpdateRateHz);
1253         }
1254     }
1255 
1256     @GuardedBy("mLock")
updateSubscriptionRateLocked(Set<Integer> updatedHalPropIds)1257     private void updateSubscriptionRateLocked(Set<Integer> updatedHalPropIds) {
1258         // This functions involves binder call to VHAL, but we intentionally keep this inside the
1259         // lock because we need to keep the subscription status consistent. If we do not use lock
1260         // here, the following situation might happen:
1261         // 1. Lock is obtained by thread 1.
1262         // 2. mHalPropIdToWaitingUpdateRequestInfo is updated by one thread to state 1.
1263         // 3. New update rate (local variable) is calculated based on state 1.
1264         // 4. Lock is released by thread 1..
1265         // 5. Lock is obtained by thread 2.
1266         // 6. mHalPropIdToWaitingUpdateRequestInfo is updated by thread 2 to state 2.
1267         // 7. New update rate (local variable) is calculated based on state 2.
1268         // 8. Lock is released by thread 2.
1269         // 9. Thread 2 calls subscribeProperty to VHAL based on state 2.
1270         // 10. Thread 1 calls subscribeProperty to VHAL based on state 1.
1271         // 11. Now internally, the state is in state 2, but from VHAL side, it is in state 1.
1272         if (updatedHalPropIds.isEmpty()) {
1273             return;
1274         }
1275         if (DBG) {
1276             Slogf.d(TAG, "updated subscription rate for hal prop IDs: %s", updatedHalPropIds);
1277         }
1278         for (int updatedHalPropId : updatedHalPropIds) {
1279             updateSubscriptionRateForHalPropIdLocked(updatedHalPropId);
1280         }
1281     }
1282 
1283     @Override
onHalEvents(List<HalPropValue> halPropValues)1284     public void onHalEvents(List<HalPropValue> halPropValues) {
1285 
1286         List<CarPropertyEvent> eventsToDispatch = new ArrayList<>();
1287 
1288         // A map to store potential succeeded set value results which is caused by the values
1289         // updated to the target values.
1290         Map<VehicleStubCallback, List<GetSetValueResult>> callbackToSetValueResults =
1291                 new ArrayMap<>();
1292 
1293         synchronized (mLock) {
1294             Set<Integer> updatedHalPropIds = new ArraySet<>();
1295             for (HalPropValue halPropValue : halPropValues) {
1296                 if (halPropValue == null) {
1297                     continue;
1298                 }
1299                 int halPropId = halPropValue.getPropId();
1300                 HalPropConfig halPropConfig = mHalPropIdToPropConfig.get(halPropId);
1301                 if (halPropConfig == null) {
1302                     Slogf.w(TAG, "onHalEvents - received HalPropValue for unsupported property: %s",
1303                             halPropIdToName(halPropId));
1304                     continue;
1305                 }
1306                 // Check payload if it is an userdebug build.
1307                 if (BuildHelper.isDebuggableBuild() && !mPropertyHalServiceIds.checkPayload(
1308                         halPropValue)) {
1309                     Slogf.w(TAG,
1310                             "Drop event for property: %s because it is failed "
1311                                     + "in payload checking.", halPropValue);
1312                     continue;
1313                 }
1314                 int mgrPropId = halToManagerPropId(halPropId);
1315                 if (DBG && halPropValue.getStatus() != VehiclePropertyStatus.AVAILABLE) {
1316                     Slogf.d(TAG, "Received event %s with status that is not AVAILABLE",
1317                             halPropValue);
1318                 }
1319                 try {
1320                     CarPropertyValue<?> carPropertyValue = halPropValue.toCarPropertyValue(
1321                             mgrPropId, halPropConfig);
1322                     CarPropertyEvent carPropertyEvent = new CarPropertyEvent(
1323                             CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, carPropertyValue);
1324                     eventsToDispatch.add(carPropertyEvent);
1325 
1326                     checkPendingWaitForUpdateRequestsLocked(halPropId, carPropertyValue,
1327                             callbackToSetValueResults, updatedHalPropIds);
1328                 } catch (IllegalStateException e) {
1329                     Slogf.w(TAG, "Drop event %s that does not have valid value", halPropValue);
1330                     continue;
1331                 }
1332             }
1333             updateSubscriptionRateLocked(updatedHalPropIds);
1334         }
1335 
1336         PropertyHalListener propertyHalListener;
1337         synchronized (mLock) {
1338             propertyHalListener = mPropertyHalListener;
1339         }
1340         if (propertyHalListener != null) {
1341             propertyHalListener.onPropertyChange(eventsToDispatch);
1342         }
1343 
1344         for (VehicleStubCallback callback : callbackToSetValueResults.keySet()) {
1345             callback.sendSetValueResults(callbackToSetValueResults.get(callback));
1346         }
1347     }
1348 
convertStatusCodeToCarSetPropertyErrorCode( @ehicleHalStatusCodeInt int vhalStatusCode)1349     private static @CarSetPropertyErrorCode int convertStatusCodeToCarSetPropertyErrorCode(
1350             @VehicleHalStatusCodeInt int vhalStatusCode) {
1351         switch (vhalStatusCode) {
1352             case STATUS_TRY_AGAIN:
1353                 return CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN;
1354             case STATUS_INVALID_ARG:
1355                 return CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG;
1356             case STATUS_NOT_AVAILABLE: // fallthrough
1357             case STATUS_NOT_AVAILABLE_DISABLED: // fallthrough
1358             case STATUS_NOT_AVAILABLE_SPEED_LOW: // fallthrough
1359             case STATUS_NOT_AVAILABLE_SPEED_HIGH: // fallthrough
1360             case STATUS_NOT_AVAILABLE_POOR_VISIBILITY: // fallthrough
1361             case STATUS_NOT_AVAILABLE_SAFETY:
1362                 return CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE;
1363             case STATUS_ACCESS_DENIED:
1364                 return CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED;
1365             default:
1366                 return CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN;
1367         }
1368     }
1369 
1370     @Override
onPropertySetError(ArrayList<VehiclePropError> vehiclePropErrors)1371     public void onPropertySetError(ArrayList<VehiclePropError> vehiclePropErrors) {
1372         PropertyHalListener propertyHalListener;
1373         synchronized (mLock) {
1374             propertyHalListener = mPropertyHalListener;
1375         }
1376         if (propertyHalListener != null) {
1377             for (int i = 0; i < vehiclePropErrors.size(); i++) {
1378                 VehiclePropError vehiclePropError = vehiclePropErrors.get(i);
1379                 int mgrPropId = halToManagerPropId(vehiclePropError.propId);
1380                 int vhalErrorCode = CarPropertyHelper.getVhalSystemErrorCode(
1381                         vehiclePropError.errorCode);
1382                 Slogf.w(TAG,
1383                         "onPropertySetError for property: %s, area ID: %d, vhal error code: %d",
1384                         VehiclePropertyIds.toString(mgrPropId), vehiclePropError.areaId,
1385                         vhalErrorCode);
1386                 @CarSetPropertyErrorCode int carPropErrorCode =
1387                         convertStatusCodeToCarSetPropertyErrorCode(vhalErrorCode);
1388                 propertyHalListener.onPropertySetError(mgrPropId, vehiclePropError.areaId,
1389                         carPropErrorCode);
1390             }
1391         }
1392         Set<Integer> updatedHalPropIds = new ArraySet<>();
1393         Map<VehicleStubCallback, List<GetSetValueResult>> callbackToSetValueResults =
1394                 new ArrayMap<>();
1395         synchronized (mLock) {
1396             for (int i = 0; i < vehiclePropErrors.size(); i++) {
1397                 VehiclePropError vehiclePropError = vehiclePropErrors.get(i);
1398                 // Fail all pending async set requests that are currently waiting for property
1399                 // update which has the same property ID and same area ID.
1400                 int halPropId = vehiclePropError.propId;
1401                 List<AsyncPropRequestInfo> pendingSetRequests =
1402                         mHalPropIdToWaitingUpdateRequestInfo.get(halPropId);
1403                 if (pendingSetRequests == null) {
1404                     continue;
1405                 }
1406                 for (int j = 0; j < pendingSetRequests.size(); j++) {
1407                     AsyncPropRequestInfo pendingRequest = pendingSetRequests.get(j);
1408                     if (pendingRequest.getAreaId() != vehiclePropError.areaId) {
1409                         continue;
1410                     }
1411                     removePendingAsyncPropRequestInfoLocked(pendingRequest, updatedHalPropIds);
1412                     int[] errorCodes = VehicleStub.convertHalToCarPropertyManagerError(
1413                             vehiclePropError.errorCode);
1414                     GetSetValueResult errorResult = pendingRequest.toErrorResult(
1415                             errorCodes[0], errorCodes[1]);
1416                     Slogf.w(TAG, "Pending async set request received property set error with "
1417                             + "error: %d, vendor error code: %d, fail the pending request: %s",
1418                             errorCodes[0], errorCodes[1], pendingRequest);
1419                     storeResultForRequest(errorResult, pendingRequest, callbackToSetValueResults);
1420                 }
1421             }
1422             updateSubscriptionRateLocked(updatedHalPropIds);
1423         }
1424         for (VehicleStubCallback callback : callbackToSetValueResults.keySet()) {
1425             callback.sendSetValueResults(callbackToSetValueResults.get(callback));
1426         }
1427     }
1428 
1429     @Override
1430     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(PrintWriter writer)1431     public void dump(PrintWriter writer) {
1432         writer.println(TAG);
1433         writer.println("  Properties available:");
1434         synchronized (mLock) {
1435             for (int i = 0; i < mHalPropIdToPropConfig.size(); i++) {
1436                 HalPropConfig halPropConfig = mHalPropIdToPropConfig.valueAt(i);
1437                 writer.println("    " + halPropConfig);
1438             }
1439         }
1440     }
1441 
prepareVehicleStubRequests(@syncRequestType int requestType, List<AsyncPropertyServiceRequest> serviceRequests, long timeoutInMs, VehicleStubCallback vehicleStubCallback, @Nullable List<AsyncPropRequestInfo> assocSetValueRequestInfo, @Nullable List<AsyncPropRequestInfo> outRequestInfo)1442     private List<AsyncGetSetRequest> prepareVehicleStubRequests(@AsyncRequestType int requestType,
1443             List<AsyncPropertyServiceRequest> serviceRequests,
1444             long timeoutInMs,
1445             VehicleStubCallback vehicleStubCallback,
1446             @Nullable List<AsyncPropRequestInfo> assocSetValueRequestInfo,
1447             @Nullable List<AsyncPropRequestInfo> outRequestInfo) {
1448         // TODO(b/242326085): Change local variables into memory pool to reduce memory
1449         //  allocation/release cycle
1450         List<AsyncGetSetRequest> vehicleStubRequests = new ArrayList<>();
1451         List<AsyncPropRequestInfo> pendingRequestInfo = new ArrayList<>();
1452         Long nowUptimeMs = SystemClock.uptimeMillis();
1453         synchronized (mLock) {
1454             for (int i = 0; i < serviceRequests.size(); i++) {
1455                 AsyncPropertyServiceRequest serviceRequest = serviceRequests.get(i);
1456                 AsyncPropRequestInfo pendingRequest = new AsyncPropRequestInfo(requestType,
1457                         serviceRequest, nowUptimeMs + timeoutInMs, vehicleStubCallback);
1458                 if (assocSetValueRequestInfo != null) {
1459                     // Link the async set value request and the get init value request together.
1460                     pendingRequest.setAssocSetValueRequestInfo(assocSetValueRequestInfo.get(i));
1461                     assocSetValueRequestInfo.get(i).setAssocGetInitValueRequestInfo(pendingRequest);
1462                 }
1463                 AsyncGetSetRequest vehicleStubRequest = generateVehicleStubAsyncRequestLocked(
1464                         pendingRequest);
1465                 vehicleStubRequests.add(vehicleStubRequest);
1466                 pendingRequestInfo.add(pendingRequest);
1467                 if (outRequestInfo != null) {
1468                     outRequestInfo.add(pendingRequest);
1469                 }
1470             }
1471             mPendingAsyncRequests.addPendingRequests(pendingRequestInfo);
1472         }
1473         return vehicleStubRequests;
1474     }
1475 
createVehicleStubCallback( IAsyncPropertyResultCallback asyncPropertyResultCallback)1476     VehicleStubCallback createVehicleStubCallback(
1477             IAsyncPropertyResultCallback asyncPropertyResultCallback) {
1478         IBinder asyncPropertyResultBinder = asyncPropertyResultCallback.asBinder();
1479         VehicleStubCallback callback;
1480         synchronized (mLock) {
1481             if (mResultBinderToVehicleStubCallback.get(asyncPropertyResultBinder) == null) {
1482                 callback = new VehicleStubCallback(asyncPropertyResultCallback);
1483                 try {
1484                     callback.linkToDeath(() -> onBinderDied(asyncPropertyResultBinder));
1485                 } catch (RemoteException e) {
1486                     throw new IllegalStateException("Linking to binder death recipient failed, "
1487                             + "the client might already died", e);
1488                 }
1489                 mResultBinderToVehicleStubCallback.put(asyncPropertyResultBinder, callback);
1490             } else {
1491                 callback = mResultBinderToVehicleStubCallback.get(asyncPropertyResultBinder);
1492             }
1493         }
1494         return callback;
1495     }
1496 
sendVehicleStubRequests(@syncRequestType int requestType, List<AsyncGetSetRequest> vehicleStubRequests, VehicleStubCallback callback)1497     private void sendVehicleStubRequests(@AsyncRequestType int requestType,
1498             List<AsyncGetSetRequest> vehicleStubRequests, VehicleStubCallback callback) {
1499         switch (requestType) {
1500             case GET: // fallthrough
1501             case GET_INITIAL_VALUE_FOR_SET:
1502                 mVehicleHal.getAsync(vehicleStubRequests, callback);
1503                 break;
1504             case SET:
1505                 mVehicleHal.setAsync(vehicleStubRequests, callback);
1506                 break;
1507         }
1508     }
1509 
1510     /**
1511      * Queries CarPropertyValue with list of AsyncPropertyServiceRequest objects.
1512      *
1513      * <p>This method gets the CarPropertyValue using async methods. </p>
1514      */
getCarPropertyValuesAsync( List<AsyncPropertyServiceRequest> serviceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1515     public void getCarPropertyValuesAsync(
1516             List<AsyncPropertyServiceRequest> serviceRequests,
1517             IAsyncPropertyResultCallback asyncPropertyResultCallback,
1518             long timeoutInMs) {
1519         VehicleStubCallback vehicleStubCallback = createVehicleStubCallback(
1520                 asyncPropertyResultCallback);
1521         List<AsyncGetSetRequest> vehicleStubRequests = prepareVehicleStubRequests(
1522                 GET, serviceRequests, timeoutInMs, vehicleStubCallback,
1523                 /* assocSetValueRequestInfo= */ null, /* outRequestInfo= */ null);
1524         sendVehicleStubRequests(GET, vehicleStubRequests, vehicleStubCallback);
1525     }
1526 
sanitizeUpdateRateHz(float updateRateHz, HalPropConfig halPropConfig)1527     private static float sanitizeUpdateRateHz(float updateRateHz, HalPropConfig halPropConfig) {
1528         float sanitizedUpdateRateHz = updateRateHz;
1529         if (halPropConfig.getChangeMode()
1530                 != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
1531             sanitizedUpdateRateHz = CarPropertyManager.SENSOR_RATE_ONCHANGE;
1532         } else if (sanitizedUpdateRateHz > halPropConfig.getMaxSampleRate()) {
1533             sanitizedUpdateRateHz = halPropConfig.getMaxSampleRate();
1534         } else if (sanitizedUpdateRateHz < halPropConfig.getMinSampleRate()) {
1535             sanitizedUpdateRateHz = halPropConfig.getMinSampleRate();
1536         }
1537         return sanitizedUpdateRateHz;
1538     }
1539 
1540     @GuardedBy("mLock")
1541     @Nullable
calcNewUpdateRateHzLocked(int halPropId)1542     private Float calcNewUpdateRateHzLocked(int halPropId) {
1543         Float maxUpdateRateHz = null;
1544         List<AsyncPropRequestInfo> requests = mHalPropIdToWaitingUpdateRequestInfo.get(halPropId);
1545         if (requests != null) {
1546             for (int i = 0; i < requests.size(); i++) {
1547                 float currentUpdateRateHz = requests.get(i).getUpdateRateHz();
1548                 if (maxUpdateRateHz == null || currentUpdateRateHz > maxUpdateRateHz) {
1549                     maxUpdateRateHz = currentUpdateRateHz;
1550                 }
1551             }
1552         }
1553         Float subscribedUpdateRateHz = mSubscribedHalPropIdToUpdateRateHz.get(halPropId);
1554         if (subscribedUpdateRateHz != null && (
1555                 maxUpdateRateHz == null || subscribedUpdateRateHz > maxUpdateRateHz)) {
1556             maxUpdateRateHz = subscribedUpdateRateHz;
1557         }
1558         return maxUpdateRateHz;
1559     }
1560 
filterWaitForUpdateRequests(List<T> requests, Function<T, Boolean> isWaitForPropertyUpdate)1561     private <T> List<T> filterWaitForUpdateRequests(List<T> requests,
1562             Function<T, Boolean> isWaitForPropertyUpdate) {
1563         List<T> waitForUpdateSetRequests = new ArrayList<>();
1564         for (int i = 0; i < requests.size(); i++) {
1565             if (isWaitForPropertyUpdate.apply(requests.get(i))) {
1566                 waitForUpdateSetRequests.add(requests.get(i));
1567             }
1568         }
1569         return waitForUpdateSetRequests;
1570     }
1571 
1572     /**
1573      * For every pending async set request that needs to wait for property update, generates an
1574      * async get initial value request and subscribes to the property update event.
1575      */
sendGetInitialValueAndSubscribeUpdateEvent( List<AsyncPropertyServiceRequest> serviceRequests, VehicleStubCallback vehicleStubCallback, long timeoutInMs, List<AsyncPropRequestInfo> waitForUpdateSetRequestInfo)1576     private void sendGetInitialValueAndSubscribeUpdateEvent(
1577             List<AsyncPropertyServiceRequest> serviceRequests,
1578             VehicleStubCallback vehicleStubCallback, long timeoutInMs,
1579             List<AsyncPropRequestInfo> waitForUpdateSetRequestInfo) {
1580         // Stores a list of async GET_INITIAL_VALUE request to be sent.
1581         List<AsyncGetSetRequest> getInitValueRequests = prepareVehicleStubRequests(
1582                 GET_INITIAL_VALUE_FOR_SET, serviceRequests, timeoutInMs,
1583                 vehicleStubCallback, /* assocSetValueRequestInfo= */ waitForUpdateSetRequestInfo,
1584                 /* outRequestInfo= */ null);
1585         Set<Integer> updatedHalPropIds = new ArraySet<>();
1586 
1587         // Subscribe to the property's change events before setting the property.
1588         synchronized (mLock) {
1589             for (AsyncPropRequestInfo setRequestInfo : waitForUpdateSetRequestInfo) {
1590                 int halPropId = managerToHalPropId(setRequestInfo.getPropertyId());
1591                 // We already checked in {@code carPropertyValueToHalPropValueLocked} inside
1592                 // {@code prepareVehicleStubRequests}, this is guaranteed not to be null.
1593                 HalPropConfig halPropConfig = mHalPropIdToPropConfig.get(halPropId);
1594 
1595                 setRequestInfo.parseClientUpdateRateHz(halPropConfig);
1596 
1597                 if (mHalPropIdToWaitingUpdateRequestInfo.get(halPropId) == null) {
1598                     mHalPropIdToWaitingUpdateRequestInfo.put(halPropId, new ArrayList<>());
1599                 }
1600 
1601                 mHalPropIdToWaitingUpdateRequestInfo.get(halPropId).add(setRequestInfo);
1602 
1603                 updatedHalPropIds.add(halPropId);
1604             }
1605             updateSubscriptionRateLocked(updatedHalPropIds);
1606         }
1607 
1608         sendVehicleStubRequests(GET_INITIAL_VALUE_FOR_SET, getInitValueRequests,
1609                 vehicleStubCallback);
1610     }
1611 
1612     /**
1613      * Sets car property values asynchronously.
1614      */
setCarPropertyValuesAsync( List<AsyncPropertyServiceRequest> serviceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1615     public void setCarPropertyValuesAsync(
1616             List<AsyncPropertyServiceRequest> serviceRequests,
1617             IAsyncPropertyResultCallback asyncPropertyResultCallback,
1618             long timeoutInMs) {
1619         List<AsyncPropRequestInfo> pendingSetRequestInfo = new ArrayList<>();
1620         VehicleStubCallback vehicleStubCallback = createVehicleStubCallback(
1621                 asyncPropertyResultCallback);
1622         List<AsyncGetSetRequest> setValueRequests = prepareVehicleStubRequests(
1623                 SET, serviceRequests, timeoutInMs, vehicleStubCallback,
1624                  /* assocSetValueRequestInfo= */ null, /* outRequestInfo= */ pendingSetRequestInfo);
1625         List<AsyncPropRequestInfo> waitForUpdateSetRequestInfo = filterWaitForUpdateRequests(
1626                 pendingSetRequestInfo, (request) -> request.isWaitForPropertyUpdate());
1627 
1628         if (waitForUpdateSetRequestInfo.size() != 0) {
1629             List<AsyncPropertyServiceRequest> waitForUpdateServiceRequests =
1630                     filterWaitForUpdateRequests(serviceRequests,
1631                             (request) -> request.isWaitForPropertyUpdate());
1632             sendGetInitialValueAndSubscribeUpdateEvent(waitForUpdateServiceRequests,
1633                     vehicleStubCallback, timeoutInMs, waitForUpdateSetRequestInfo);
1634         }
1635 
1636         sendVehicleStubRequests(SET, setValueRequests, vehicleStubCallback);
1637     }
1638 
1639     /**
1640      * Maps managerRequestIds to serviceRequestIds and remove them from the pending request map.
1641      */
cancelRequests(int[] managerRequestIds)1642     public void cancelRequests(int[] managerRequestIds) {
1643         List<Integer> serviceRequestIdsToCancel = new ArrayList<>();
1644         Set<Integer> managerRequestIdsSet = new ArraySet<>();
1645         for (int i = 0; i < managerRequestIds.length; i++) {
1646             managerRequestIdsSet.add(managerRequestIds[i]);
1647         }
1648         synchronized (mLock) {
1649             for (int i = 0; i < mPendingAsyncRequests.size(); i++) {
1650                 // For GET_INITIAL_VALUE request, they have the same manager request ID as their
1651                 // associated async set request. While cancelling the async set request, they will
1652                 // be cancelled as well, see {@link cleanupPendingAsyncSetRequestLocked}, so no need
1653                 // to cancel them here.
1654                 AsyncPropRequestInfo propRequestInfo = mPendingAsyncRequests.valueAt(i);
1655                 if (managerRequestIdsSet.contains(propRequestInfo.getManagerRequestId())
1656                         && propRequestInfo.getRequestType() != GET_INITIAL_VALUE_FOR_SET) {
1657                     serviceRequestIdsToCancel.add((int) mPendingAsyncRequests.keyAt(i));
1658                 }
1659             }
1660             cancelRequestsByServiceRequestIdsLocked(serviceRequestIdsToCancel);
1661         }
1662         if (!serviceRequestIdsToCancel.isEmpty()) {
1663             mVehicleHal.cancelRequests(serviceRequestIdsToCancel);
1664         }
1665     }
1666 
onBinderDied(IBinder binder)1667     private void onBinderDied(IBinder binder) {
1668         List<Integer> serviceRequestIdsToCancel = new ArrayList<>();
1669         synchronized (mLock) {
1670             mResultBinderToVehicleStubCallback.remove(binder);
1671             for (int i = 0; i < mPendingAsyncRequests.size(); i++) {
1672                 AsyncPropRequestInfo clientRequestInfo = mPendingAsyncRequests.valueAt(i);
1673                 if (clientRequestInfo.getVehicleStubCallback().getClientBinder() != binder) {
1674                     continue;
1675                 }
1676                 serviceRequestIdsToCancel.add((int) mPendingAsyncRequests.keyAt(i));
1677             }
1678             cancelRequestsByServiceRequestIdsLocked(serviceRequestIdsToCancel);
1679         }
1680         if (!serviceRequestIdsToCancel.isEmpty()) {
1681             mVehicleHal.cancelRequests(serviceRequestIdsToCancel);
1682         }
1683     }
1684 
1685     @GuardedBy("mLock")
cancelRequestsByServiceRequestIdsLocked(List<Integer> serviceRequestIdsToCancel)1686     private void cancelRequestsByServiceRequestIdsLocked(List<Integer> serviceRequestIdsToCancel) {
1687         if (serviceRequestIdsToCancel.isEmpty()) {
1688             return;
1689         }
1690         Set<Integer> updatedHalPropIds = new ArraySet<>();
1691         for (int i = 0; i < serviceRequestIdsToCancel.size(); i++) {
1692             Slogf.w(TAG, "the request for propertyHalService request ID: %d is cancelled",
1693                     serviceRequestIdsToCancel.get(i));
1694             getAndRemovePendingAsyncPropRequestInfoLocked(
1695                     serviceRequestIdsToCancel.get(i), updatedHalPropIds);
1696         }
1697         updateSubscriptionRateLocked(updatedHalPropIds);
1698     }
1699 
1700     @GuardedBy("mLock")
carPropertyValueToHalPropValueLocked(CarPropertyValue carPropertyValue)1701     private HalPropValue carPropertyValueToHalPropValueLocked(CarPropertyValue carPropertyValue) {
1702         int mgrPropId = carPropertyValue.getPropertyId();
1703         int halPropId = managerToHalPropId(mgrPropId);
1704         HalPropConfig halPropConfig = mHalPropIdToPropConfig.get(halPropId);
1705         if (halPropConfig == null) {
1706             throw new IllegalArgumentException("Property ID: " + mgrPropId + " is not supported");
1707         }
1708         return mPropValueBuilder.build(carPropertyValue, halPropId, halPropConfig);
1709     }
1710 
halPropIdToName(int halPropId)1711     private String halPropIdToName(int halPropId) {
1712         return VehiclePropertyIds.toString(halToManagerPropId(halPropId));
1713     }
1714 
1715     /**
1716      * Get the pending async requests size.
1717      *
1718      * For test only.
1719      */
countPendingAsyncRequests()1720     public int countPendingAsyncRequests() {
1721         synchronized (mLock) {
1722             return mPendingAsyncRequests.size();
1723         }
1724     }
1725 
1726     /**
1727      * Get the size of the map from hal prop ID to pending async set value requests.
1728      *
1729      * For test only.
1730      */
countHalPropIdToWaitForUpdateRequests()1731     public int countHalPropIdToWaitForUpdateRequests() {
1732         synchronized (mLock) {
1733             return mHalPropIdToWaitingUpdateRequestInfo.size();
1734         }
1735     }
1736 }
1737