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 
17 package com.android.car;
18 
19 import static android.car.hardware.CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS;
20 
21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
22 import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY;
23 import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN;
24 import static com.android.car.internal.property.CarPropertyHelper.propertyIdsToString;
25 
26 import static java.lang.Integer.toHexString;
27 import static java.util.Objects.requireNonNull;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.car.VehiclePropertyIds;
32 import android.car.builtin.os.TraceHelper;
33 import android.car.builtin.util.Slogf;
34 import android.car.feature.FeatureFlags;
35 import android.car.feature.FeatureFlagsImpl;
36 import android.car.hardware.CarHvacFanDirection;
37 import android.car.hardware.CarPropertyConfig;
38 import android.car.hardware.CarPropertyValue;
39 import android.car.hardware.property.AreaIdConfig;
40 import android.car.hardware.property.CarPropertyEvent;
41 import android.car.hardware.property.CruiseControlType;
42 import android.car.hardware.property.ErrorState;
43 import android.car.hardware.property.EvStoppingMode;
44 import android.car.hardware.property.ICarProperty;
45 import android.car.hardware.property.ICarPropertyEventListener;
46 import android.car.hardware.property.WindshieldWipersSwitch;
47 import android.content.Context;
48 import android.os.Handler;
49 import android.os.HandlerThread;
50 import android.os.IBinder;
51 import android.os.RemoteException;
52 import android.os.ServiceSpecificException;
53 import android.os.SystemClock;
54 import android.os.Trace;
55 import android.util.ArrayMap;
56 import android.util.ArraySet;
57 import android.util.Log;
58 import android.util.SparseArray;
59 import android.util.proto.ProtoOutputStream;
60 
61 import com.android.car.hal.PropertyHalService;
62 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
63 import com.android.car.internal.property.AsyncPropertyServiceRequest;
64 import com.android.car.internal.property.AsyncPropertyServiceRequestList;
65 import com.android.car.internal.property.CarPropertyConfigList;
66 import com.android.car.internal.property.CarPropertyErrorCodes;
67 import com.android.car.internal.property.CarPropertyHelper;
68 import com.android.car.internal.property.CarSubscription;
69 import com.android.car.internal.property.GetPropertyConfigListResult;
70 import com.android.car.internal.property.IAsyncPropertyResultCallback;
71 import com.android.car.internal.property.InputSanitizationUtils;
72 import com.android.car.internal.property.SubscriptionManager;
73 import com.android.car.internal.util.ArrayUtils;
74 import com.android.car.internal.util.IndentingPrintWriter;
75 import com.android.car.internal.util.IntArray;
76 import com.android.car.logging.HistogramFactoryInterface;
77 import com.android.car.logging.SystemHistogramFactory;
78 import com.android.car.property.CarPropertyServiceClient;
79 import com.android.internal.annotations.GuardedBy;
80 import com.android.internal.annotations.VisibleForTesting;
81 import com.android.internal.util.Preconditions;
82 import com.android.modules.expresslog.Histogram;
83 
84 import java.util.ArrayList;
85 import java.util.Arrays;
86 import java.util.HashSet;
87 import java.util.List;
88 import java.util.Map;
89 import java.util.Objects;
90 import java.util.Set;
91 import java.util.concurrent.Callable;
92 import java.util.concurrent.CountDownLatch;
93 import java.util.concurrent.TimeUnit;
94 
95 /**
96  * This class implements the binder interface for ICarProperty.aidl to make it easier to create
97  * multiple managers that deal with Vehicle Properties. The property Ids in this class are IDs in
98  * manager level.
99  */
100 public class CarPropertyService extends ICarProperty.Stub
101         implements CarServiceBase, PropertyHalService.PropertyHalListener {
102     private static final String TAG = CarLog.tagFor(CarPropertyService.class);
103     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
104     // Maximum count of sync get/set property operation allowed at once. The reason we limit this
105     // is because each sync get/set property operation takes up one binder thread. If they take
106     // all the binder thread, we do not have thread left for the result callback from VHAL. This
107     // will cause all the pending sync operation to timeout because result cannot be delivered.
108     private static final int SYNC_GET_SET_PROPERTY_OP_LIMIT = 16;
109     private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE;
110     // A list of properties that must not set waitForPropertyUpdate to {@code true} for set async.
111     private static final Set<Integer> NOT_ALLOWED_WAIT_FOR_UPDATE_PROPERTIES =
112             new HashSet<>(Arrays.asList(
113                 VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION
114             ));
115 
116     private static final Set<Integer> ERROR_STATES =
117             new HashSet<Integer>(Arrays.asList(
118                     ErrorState.OTHER_ERROR_STATE,
119                     ErrorState.NOT_AVAILABLE_DISABLED,
120                     ErrorState.NOT_AVAILABLE_SPEED_LOW,
121                     ErrorState.NOT_AVAILABLE_SPEED_HIGH,
122                     ErrorState.NOT_AVAILABLE_SAFETY
123             ));
124     private static final Set<Integer> CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES =
125             new HashSet<Integer>(Arrays.asList(
126                     CarHvacFanDirection.UNKNOWN
127             ));
128     private static final Set<Integer> CRUISE_CONTROL_TYPE_UNWRITABLE_STATES =
129             new HashSet<Integer>(Arrays.asList(
130                     CruiseControlType.OTHER
131             ));
132     static {
133         CRUISE_CONTROL_TYPE_UNWRITABLE_STATES.addAll(ERROR_STATES);
134     }
135     private static final Set<Integer> EV_STOPPING_MODE_UNWRITABLE_STATES =
136             new HashSet<Integer>(Arrays.asList(
137                     EvStoppingMode.STATE_OTHER
138             ));
139     private static final Set<Integer> WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES =
140             new HashSet<Integer>(Arrays.asList(
141                     WindshieldWipersSwitch.OTHER
142             ));
143 
144     private static final SparseArray<Set<Integer>> PROPERTY_ID_TO_UNWRITABLE_STATES =
145             new SparseArray<>();
146     static {
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.CRUISE_CONTROL_TYPE, CRUISE_CONTROL_TYPE_UNWRITABLE_STATES)147         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
148                 VehiclePropertyIds.CRUISE_CONTROL_TYPE,
149                 CRUISE_CONTROL_TYPE_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.EV_STOPPING_MODE, EV_STOPPING_MODE_UNWRITABLE_STATES)150         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
151                 VehiclePropertyIds.EV_STOPPING_MODE,
152                 EV_STOPPING_MODE_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.HVAC_FAN_DIRECTION, CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES)153         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
154                 VehiclePropertyIds.HVAC_FAN_DIRECTION,
155                 CAR_HVAC_FAN_DIRECTION_UNWRITABLE_STATES);
PROPERTY_ID_TO_UNWRITABLE_STATES.put( VehiclePropertyIds.WINDSHIELD_WIPERS_SWITCH, WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES)156         PROPERTY_ID_TO_UNWRITABLE_STATES.put(
157                 VehiclePropertyIds.WINDSHIELD_WIPERS_SWITCH,
158                 WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES);
159     }
160 
161     private final FeatureFlags mFeatureFlags;
162     private final HistogramFactoryInterface mHistogramFactory;
163 
164     private Histogram mConcurrentSyncOperationHistogram;
165     private Histogram mGetPropertySyncLatencyHistogram;
166     private Histogram mSetPropertySyncLatencyHistogram;
167     private Histogram mSubscriptionUpdateRateHistogram;
168     private Histogram mGetAsyncLatencyHistogram;
169     private Histogram mSetAsyncLatencyHistogram;
170 
171     private final Context mContext;
172     private final PropertyHalService mPropertyHalService;
173     private final Object mLock = new Object();
174     @GuardedBy("mLock")
175     private final Map<IBinder, CarPropertyServiceClient> mClientMap = new ArrayMap<>();
176     @GuardedBy("mLock")
177     private final SubscriptionManager<CarPropertyServiceClient> mSubscriptionManager =
178             new SubscriptionManager<>();
179     @GuardedBy("mLock")
180     private final SparseArray<SparseArray<CarPropertyServiceClient>> mSetOpClientByAreaIdByPropId =
181             new SparseArray<>();
182     private final HandlerThread mHandlerThread =
183             CarServiceUtils.getHandlerThread(getClass().getSimpleName());
184     private final Handler mHandler = new Handler(mHandlerThread.getLooper());
185     // Use SparseArray instead of map to save memory.
186     @GuardedBy("mLock")
187     private SparseArray<CarPropertyConfig<?>> mPropertyIdToCarPropertyConfig = new SparseArray<>();
188     @GuardedBy("mLock")
189     private int mSyncGetSetPropertyOpCount;
190 
191     /**
192      * The builder for {@link com.android.car.CarPropertyService}.
193      */
194     public static final class Builder {
195         private Context mContext;
196         private PropertyHalService mPropertyHalService;
197         private @Nullable FeatureFlags mFeatureFlags;
198         private @Nullable HistogramFactoryInterface mHistogramFactory;
199         private boolean mBuilt;
200 
201         /** Sets the context. */
setContext(Context context)202         public Builder setContext(Context context) {
203             mContext = context;
204             return this;
205         }
206 
207         /** Sets the {@link PropertyHalService}. */
setPropertyHalService(PropertyHalService propertyHalService)208         public Builder setPropertyHalService(PropertyHalService propertyHalService) {
209             mPropertyHalService = propertyHalService;
210             return this;
211         }
212 
213         /**
214          * Builds the {@link com.android.car.CarPropertyService}.
215          */
build()216         public CarPropertyService build() {
217             if (mBuilt) {
218                 throw new IllegalStateException("Only allowed to be built once");
219             }
220             mBuilt = true;
221             return new CarPropertyService(this);
222         }
223 
224         /** Sets fake feature flag for unit testing. */
225         @VisibleForTesting
setFeatureFlags(FeatureFlags fakeFeatureFlags)226         Builder setFeatureFlags(FeatureFlags fakeFeatureFlags) {
227             mFeatureFlags = fakeFeatureFlags;
228             return this;
229         }
230 
231         /** Sets fake histogram builder for unit testing. */
232         @VisibleForTesting
setHistogramFactory(HistogramFactoryInterface histogramFactory)233         Builder setHistogramFactory(HistogramFactoryInterface histogramFactory) {
234             mHistogramFactory = histogramFactory;
235             return this;
236         }
237     }
238 
CarPropertyService(Builder builder)239     private CarPropertyService(Builder builder) {
240         if (DBG) {
241             Slogf.d(TAG, "CarPropertyService started!");
242         }
243         mPropertyHalService = Objects.requireNonNull(builder.mPropertyHalService);
244         mContext = Objects.requireNonNull(builder.mContext);
245         mFeatureFlags = Objects.requireNonNullElseGet(builder.mFeatureFlags,
246                 () -> new FeatureFlagsImpl());
247         mHistogramFactory = Objects.requireNonNullElseGet(builder.mHistogramFactory,
248                 () -> new SystemHistogramFactory());
249         initializeHistogram();
250     }
251 
252     @VisibleForTesting
finishHandlerTasks(int timeoutInMs)253     void finishHandlerTasks(int timeoutInMs) throws InterruptedException {
254         CountDownLatch cdLatch = new CountDownLatch(1);
255         mHandler.post(() -> {
256             cdLatch.countDown();
257         });
258         cdLatch.await(timeoutInMs, TimeUnit.MILLISECONDS);
259     }
260 
261     @Override
init()262     public void init() {
263         synchronized (mLock) {
264             // Cache the configs list to avoid subsequent binder calls
265             mPropertyIdToCarPropertyConfig = mPropertyHalService.getPropertyList();
266             if (DBG) {
267                 Slogf.d(TAG, "cache CarPropertyConfigs " + mPropertyIdToCarPropertyConfig.size());
268             }
269         }
270         mPropertyHalService.setPropertyHalListener(this);
271     }
272 
273     @Override
release()274     public void release() {
275         synchronized (mLock) {
276             mClientMap.clear();
277             mSubscriptionManager.clear();
278             mPropertyHalService.setPropertyHalListener(null);
279             mSetOpClientByAreaIdByPropId.clear();
280         }
281     }
282 
283     @Override
284     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)285     public void dump(IndentingPrintWriter writer) {
286         writer.println("*CarPropertyService*");
287         writer.increaseIndent();
288         synchronized (mLock) {
289             writer.println("There are " + mClientMap.size() + " clients that have registered"
290                     + " listeners in CarPropertyService.");
291             writer.println("Current sync operation count: " + mSyncGetSetPropertyOpCount);
292             writer.println("Properties registered: ");
293             writer.increaseIndent();
294             mSubscriptionManager.dump(writer);
295             writer.decreaseIndent();
296             writer.println("Properties that have a listener registered for setProperty:");
297             writer.increaseIndent();
298             for (int i = 0; i < mSetOpClientByAreaIdByPropId.size(); i++) {
299                 int propId = mSetOpClientByAreaIdByPropId.keyAt(i);
300                 SparseArray areaIdToClient = mSetOpClientByAreaIdByPropId.valueAt(i);
301                 for (int j = 0; j < areaIdToClient.size(); j++) {
302                     int areaId = areaIdToClient.keyAt(j);
303                     writer.println("Client: " + areaIdToClient.valueAt(j).hashCode() + " propId: "
304                             + VehiclePropertyIds.toString(propId)  + " areaId: 0x"
305                             + toHexString(areaId));
306                 }
307             }
308             writer.decreaseIndent();
309         }
310         writer.decreaseIndent();
311     }
312 
313     @Override
314     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)315     public void dumpProto(ProtoOutputStream proto) {}
316 
317     /**
318      * Subscribes to the property update events for the property ID.
319      *
320      * Used internally in car service.
321      */
registerListener(int propertyId, float updateRateHz, boolean enableVariableUpdateRate, float resolution, ICarPropertyEventListener carPropertyEventListener)322     public void registerListener(int propertyId, float updateRateHz,
323             boolean enableVariableUpdateRate, float resolution,
324             ICarPropertyEventListener carPropertyEventListener) {
325         CarSubscription option = new CarSubscription();
326         int[] areaIds = EMPTY_INT_ARRAY;
327         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
328         // carPropertyConfig nullity check will be done in registerListener
329         if (carPropertyConfig != null) {
330             areaIds = carPropertyConfig.getAreaIds();
331         }
332         option.propertyId = propertyId;
333         option.updateRateHz = updateRateHz;
334         option.areaIds = areaIds;
335         option.enableVariableUpdateRate = enableVariableUpdateRate;
336         option.resolution = resolution;
337         registerListener(List.of(option), carPropertyEventListener);
338     }
339 
340     /**
341      * Subscribes to the property update events for the property ID at a resolution of 0.
342      *
343      * Used internally in car service.
344      */
registerListener(int propertyId, float updateRateHz, boolean enableVariableUpdateRate, ICarPropertyEventListener carPropertyEventListener)345     public void registerListener(int propertyId, float updateRateHz,
346             boolean enableVariableUpdateRate,
347             ICarPropertyEventListener carPropertyEventListener) {
348         registerListener(propertyId, updateRateHz, enableVariableUpdateRate, /* resolution */ 0.0f,
349                 carPropertyEventListener);
350     }
351 
352     /**
353      * Subscribes to the property update events for the property ID with VUR enabled for continuous
354      * property and a resolution of 0.
355      *
356      * Used internally in car service.
357      */
registerListener(int propertyId, float updateRateHz, ICarPropertyEventListener carPropertyEventListener)358     public void registerListener(int propertyId, float updateRateHz,
359             ICarPropertyEventListener carPropertyEventListener)
360             throws IllegalArgumentException, ServiceSpecificException {
361         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
362         boolean enableVariableUpdateRate = false;
363         // carPropertyConfig nullity check will be done in registerListener
364         if (carPropertyConfig != null
365                 && carPropertyConfig.getChangeMode() == VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
366             enableVariableUpdateRate = true;
367         }
368         registerListener(propertyId, updateRateHz, enableVariableUpdateRate, /* resolution */ 0.0f,
369                 carPropertyEventListener);
370     }
371 
372     /**
373      * Validates the subscribe options and sanitize the update rate inside it.
374      *
375      * The update rate will be fit within the {@code minSampleRate} and {@code maxSampleRate}.
376      *
377      * @throws IllegalArgumentException if one of the options is not valid.
378      */
validateAndSanitizeSubscriptions( List<CarSubscription> carSubscriptions)379     private List<CarSubscription> validateAndSanitizeSubscriptions(
380                 List<CarSubscription> carSubscriptions)
381             throws IllegalArgumentException {
382         List<CarSubscription> sanitizedSubscriptions = new ArrayList<>();
383         for (int i = 0; i < carSubscriptions.size(); i++) {
384             CarSubscription subscription = carSubscriptions.get(i);
385             CarPropertyConfig<?> carPropertyConfig = validateRegisterParameterAndGetConfig(
386                     subscription.propertyId, subscription.areaIds);
387             subscription.updateRateHz = InputSanitizationUtils.sanitizeUpdateRateHz(
388                     carPropertyConfig, subscription.updateRateHz);
389             subscription.resolution = InputSanitizationUtils.sanitizeResolution(
390                     mFeatureFlags, carPropertyConfig, subscription.resolution);
391             sanitizedSubscriptions.addAll(InputSanitizationUtils.sanitizeEnableVariableUpdateRate(
392                     mFeatureFlags, carPropertyConfig, subscription));
393         }
394         return sanitizedSubscriptions;
395     }
396 
397     /**
398      * Gets the {@code CarPropertyServiceClient} for the binder, create a new one if not exists.
399      *
400      * @param carPropertyEventListener The client callback.
401      * @return the client for the binder, or null if the client is already dead.
402      */
403     @GuardedBy("mLock")
getOrCreateClientForBinderLocked( ICarPropertyEventListener carPropertyEventListener)404     private @Nullable CarPropertyServiceClient getOrCreateClientForBinderLocked(
405             ICarPropertyEventListener carPropertyEventListener) {
406         IBinder listenerBinder = carPropertyEventListener.asBinder();
407         CarPropertyServiceClient client = mClientMap.get(listenerBinder);
408         if (client != null) {
409             return client;
410         }
411         client = new CarPropertyServiceClient(carPropertyEventListener,
412                 this::unregisterListenerBinderForProps);
413         if (client.isDead()) {
414             Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
415             return null;
416         }
417         mClientMap.put(listenerBinder, client);
418         return client;
419     }
420 
421     @Override
registerListener(List<CarSubscription> carSubscriptions, ICarPropertyEventListener carPropertyEventListener)422     public void registerListener(List<CarSubscription> carSubscriptions,
423             ICarPropertyEventListener carPropertyEventListener)
424             throws IllegalArgumentException, ServiceSpecificException {
425         requireNonNull(carSubscriptions);
426         requireNonNull(carPropertyEventListener);
427 
428         List<CarSubscription> sanitizedOptions =
429                 validateAndSanitizeSubscriptions(carSubscriptions);
430 
431         CarPropertyServiceClient finalClient;
432         synchronized (mLock) {
433             // We create the client first so that we will not subscribe if the binder is already
434             // dead.
435             CarPropertyServiceClient client = getOrCreateClientForBinderLocked(
436                     carPropertyEventListener);
437             if (client == null) {
438                 // The client is already dead.
439                 return;
440             }
441 
442             for (int i = 0; i < sanitizedOptions.size(); i++) {
443                 CarSubscription option = sanitizedOptions.get(i);
444                 mSubscriptionUpdateRateHistogram.logSample(option.updateRateHz);
445                 if (DBG) {
446                     Slogf.d(TAG, "registerListener after update rate sanitization, options: "
447                             + sanitizedOptions.get(i));
448                 }
449             }
450 
451             // Store the new subscritpion state in the staging area. This does not affect the
452             // current state.
453             mSubscriptionManager.stageNewOptions(client, sanitizedOptions);
454 
455             // Try to apply the staged changes.
456             try {
457                 applyStagedChangesLocked();
458             } catch (Exception e) {
459                 mSubscriptionManager.dropCommit();
460                 throw e;
461             }
462 
463             // After subscribeProperty succeeded, adds the client to the
464             // [propertyId -> subscribed clients list] map. Adds the property to the client's
465             // [areaId -> update rate] map.
466             mSubscriptionManager.commit();
467             for (int i = 0; i < sanitizedOptions.size(); i++) {
468                 CarSubscription option = sanitizedOptions.get(i);
469                 // After {@code validateAndSanitizeSubscriptions}, update rate must be 0 for
470                 // on-change property and non-0 for continuous property.
471                 if (option.updateRateHz != 0) {
472                     client.addContinuousProperty(
473                             option.propertyId, option.areaIds, option.updateRateHz,
474                             option.enableVariableUpdateRate, option.resolution);
475                 } else {
476                     client.addOnChangeProperty(option.propertyId, option.areaIds);
477                 }
478             }
479             finalClient = client;
480         }
481 
482         mHandler.post(() ->
483                 getAndDispatchPropertyInitValue(sanitizedOptions, finalClient));
484     }
485 
486     /**
487      * Register property listener for car service's internal usage.
488      *
489      * This function catches all exceptions and return {@code true} if succeed.
490      */
registerListenerSafe(int propertyId, float updateRateHz, boolean enableVariableUpdateRate, ICarPropertyEventListener iCarPropertyEventListener)491     public boolean registerListenerSafe(int propertyId, float updateRateHz,
492             boolean enableVariableUpdateRate,
493             ICarPropertyEventListener iCarPropertyEventListener) {
494         try {
495             registerListener(propertyId, updateRateHz, enableVariableUpdateRate,
496                     iCarPropertyEventListener);
497             return true;
498         } catch (Exception e) {
499             Slogf.e(TAG, e, "registerListenerSafe() failed for property ID: %s updateRateHz: %f",
500                     VehiclePropertyIds.toString(propertyId), updateRateHz);
501             return false;
502         }
503     }
504 
505     /**
506      * Register property listener for car service's internal usage with VUR enabled for continuous
507      * property.
508      *
509      * This function catches all exceptions and return {@code true} if succeed.
510      */
registerListenerSafe(int propertyId, float updateRateHz, ICarPropertyEventListener iCarPropertyEventListener)511     public boolean registerListenerSafe(int propertyId, float updateRateHz,
512             ICarPropertyEventListener iCarPropertyEventListener) {
513         try {
514             registerListener(propertyId, updateRateHz, iCarPropertyEventListener);
515             return true;
516         } catch (Exception e) {
517             Slogf.e(TAG, e, "registerListenerSafe() failed for property ID: %s updateRateHz: %f",
518                     VehiclePropertyIds.toString(propertyId), updateRateHz);
519             return false;
520         }
521     }
522 
523     @GuardedBy("mLock")
applyStagedChangesLocked()524     void applyStagedChangesLocked() throws ServiceSpecificException {
525         List<CarSubscription> filteredSubscriptions = new ArrayList<>();
526         List<Integer> propertyIdsToUnsubscribe = new ArrayList<>();
527         mSubscriptionManager.diffBetweenCurrentAndStage(/* out */ filteredSubscriptions,
528                 /* out */ propertyIdsToUnsubscribe);
529 
530         if (DBG) {
531             Slogf.d(TAG, "Subscriptions after filtering out options that are already"
532                     + " subscribed at the same or a higher rate: " + filteredSubscriptions);
533         }
534 
535         if (!filteredSubscriptions.isEmpty()) {
536             try {
537                 mPropertyHalService.subscribeProperty(filteredSubscriptions);
538             } catch (ServiceSpecificException e) {
539                 Slogf.e(TAG, "PropertyHalService.subscribeProperty failed", e);
540                 throw e;
541             }
542         }
543 
544         for (int i = 0; i < propertyIdsToUnsubscribe.size(); i++) {
545             Slogf.d(TAG, "Property: %s is no longer subscribed",
546                     propertyIdsToUnsubscribe.get(i));
547             try {
548                 mPropertyHalService.unsubscribeProperty(propertyIdsToUnsubscribe.get(i));
549             } catch (ServiceSpecificException e) {
550                 Slogf.e(TAG, "failed to call PropertyHalService.unsubscribeProperty", e);
551                 throw e;
552             }
553         }
554     }
555 
getAndDispatchPropertyInitValue(List<CarSubscription> carSubscriptions, CarPropertyServiceClient client)556     private void getAndDispatchPropertyInitValue(List<CarSubscription> carSubscriptions,
557             CarPropertyServiceClient client) {
558         List<CarPropertyEvent> events = new ArrayList<>();
559         for (int i = 0; i < carSubscriptions.size(); i++) {
560             CarSubscription option = carSubscriptions.get(i);
561             int propertyId = option.propertyId;
562             int[] areaIds = option.areaIds;
563             for (int areaId : areaIds) {
564                 CarPropertyValue carPropertyValue = null;
565                 try {
566                     carPropertyValue = getProperty(propertyId, areaId);
567                 } catch (ServiceSpecificException e) {
568                     Slogf.w(TAG, "Get initial carPropertyValue for registerCallback failed -"
569                                     + " property ID: %s, area ID %s, exception: %s",
570                             VehiclePropertyIds.toString(propertyId), Integer.toHexString(areaId),
571                             e);
572                     int errorCode = CarPropertyErrorCodes.getVhalSystemErrorCode(e.errorCode);
573                     long timestampNanos = SystemClock.elapsedRealtimeNanos();
574                     CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
575                     Object defaultValue = CarPropertyHelper.getDefaultValue(
576                             carPropertyConfig.getPropertyType());
577                     if (CarPropertyErrorCodes.isNotAvailableVehicleHalStatusCode(errorCode)) {
578                         carPropertyValue = new CarPropertyValue<>(propertyId, areaId,
579                                 CarPropertyValue.STATUS_UNAVAILABLE, timestampNanos, defaultValue);
580                     } else {
581                         carPropertyValue = new CarPropertyValue<>(propertyId, areaId,
582                                 CarPropertyValue.STATUS_ERROR, timestampNanos, defaultValue);
583                     }
584                 } catch (Exception e) {
585                     // Do nothing.
586                     Slogf.e(TAG, "Get initial carPropertyValue for registerCallback failed -"
587                                     + " property ID: %s, area ID %s, exception: %s",
588                             VehiclePropertyIds.toString(propertyId), Integer.toHexString(areaId),
589                             e);
590                 }
591                 if (carPropertyValue != null) {
592                     CarPropertyEvent event = new CarPropertyEvent(
593                             CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, carPropertyValue);
594                     events.add(event);
595                 }
596             }
597         }
598 
599         if (events.isEmpty()) {
600             return;
601         }
602         try {
603             client.onEvent(events);
604         } catch (RemoteException ex) {
605             // If we cannot send a record, it's likely the connection snapped. Let the binder
606             // death handle the situation.
607             Slogf.e(TAG, "onEvent calling failed", ex);
608         }
609     }
610 
611     @Override
unregisterListener(int propertyId, ICarPropertyEventListener iCarPropertyEventListener)612     public void unregisterListener(int propertyId,
613             ICarPropertyEventListener iCarPropertyEventListener)
614             throws IllegalArgumentException, ServiceSpecificException {
615         requireNonNull(iCarPropertyEventListener);
616 
617         // We do not have to call validateRegisterParameterAndGetConfig since if the property was
618         // previously subscribed, then the client already had the read permssion. If not, then we
619         // would do nothing.
620         // We also need to consider the case where the client has write-only permission and uses
621         // setProperty before, we must remove the listener associated with property set error.
622         assertConfigNotNullAndGetConfig(propertyId);
623 
624         if (DBG) {
625             Slogf.d(TAG,
626                     "unregisterListener property ID=" + VehiclePropertyIds.toString(propertyId));
627         }
628 
629         IBinder listenerBinder = iCarPropertyEventListener.asBinder();
630         unregisterListenerBinderForProps(List.of(propertyId), listenerBinder);
631     }
632 
633     /**
634      * Unregister property listener for car service's internal usage.
635      */
unregisterListenerSafe(int propertyId, ICarPropertyEventListener iCarPropertyEventListener)636     public boolean unregisterListenerSafe(int propertyId,
637             ICarPropertyEventListener iCarPropertyEventListener) {
638         try {
639             unregisterListener(propertyId, iCarPropertyEventListener);
640             return true;
641         } catch (Exception e) {
642             Slogf.e(TAG, e, "unregisterListenerSafe() failed for property ID: %s",
643                     VehiclePropertyIds.toString(propertyId));
644             return false;
645         }
646     }
647 
unregisterListenerBinderForProps(List<Integer> propertyIds, IBinder listenerBinder)648     private void unregisterListenerBinderForProps(List<Integer> propertyIds, IBinder listenerBinder)
649             throws ServiceSpecificException {
650         synchronized (mLock) {
651             CarPropertyServiceClient client = mClientMap.get(listenerBinder);
652             if (client == null) {
653                 Slogf.e(TAG, "unregisterListener: Listener was not previously "
654                         + "registered for any property");
655                 return;
656             }
657 
658             ArraySet<Integer> validPropertyIds = new ArraySet<>();
659             for (int i = 0; i < propertyIds.size(); i++) {
660                 int propertyId = propertyIds.get(i);
661                 if (mPropertyIdToCarPropertyConfig.get(propertyId) == null) {
662                     // Do not attempt to unregister an invalid propertyId
663                     Slogf.e(TAG, "unregisterListener: propertyId is not in config list: %s",
664                             VehiclePropertyIds.toString(propertyId));
665                     continue;
666                 }
667                 validPropertyIds.add(propertyId);
668             }
669 
670             if (validPropertyIds.isEmpty()) {
671                 Slogf.e(TAG, "All properties are invalid: " + propertyIdsToString(propertyIds));
672                 return;
673             }
674 
675             // Clear the onPropertySetError callback associated with this property.
676             clearSetOperationRecorderLocked(validPropertyIds, client);
677 
678             mSubscriptionManager.stageUnregister(client, validPropertyIds);
679 
680             try {
681                 applyStagedChangesLocked();
682             } catch (Exception e) {
683                 mSubscriptionManager.dropCommit();
684                 throw e;
685             }
686 
687             mSubscriptionManager.commit();
688             boolean allPropertiesRemoved = client.remove(validPropertyIds);
689             if (allPropertiesRemoved) {
690                 mClientMap.remove(listenerBinder);
691             }
692         }
693     }
694 
695     /**
696      * Return the list of properties' configs that the caller may access.
697      */
698     @NonNull
699     @Override
getPropertyList()700     public CarPropertyConfigList getPropertyList() {
701         int[] allPropIds;
702         // Avoid permission checking under lock.
703         synchronized (mLock) {
704             allPropIds = new int[mPropertyIdToCarPropertyConfig.size()];
705             for (int i = 0; i < mPropertyIdToCarPropertyConfig.size(); i++) {
706                 allPropIds[i] = mPropertyIdToCarPropertyConfig.keyAt(i);
707             }
708         }
709         return getPropertyConfigList(allPropIds).carPropertyConfigList;
710     }
711 
712     /**
713      * @param propIds Array of property Ids
714      * @return the list of properties' configs that the caller may access.
715      */
716     @NonNull
717     @Override
getPropertyConfigList(int[] propIds)718     public GetPropertyConfigListResult getPropertyConfigList(int[] propIds) {
719         GetPropertyConfigListResult result = new GetPropertyConfigListResult();
720         List<CarPropertyConfig> availableProp = new ArrayList<>();
721         IntArray missingPermissionPropIds = new IntArray(availableProp.size());
722         IntArray unsupportedPropIds = new IntArray(availableProp.size());
723 
724         synchronized (mLock) {
725             for (int propId : propIds) {
726                 if (!mPropertyIdToCarPropertyConfig.contains(propId)) {
727                     unsupportedPropIds.add(propId);
728                     continue;
729                 }
730 
731                 if (!mPropertyHalService.isReadable(mContext, propId)
732                         && !mPropertyHalService.isWritable(mContext, propId)) {
733                     missingPermissionPropIds.add(propId);
734                     continue;
735                 }
736 
737                 availableProp.add(mPropertyIdToCarPropertyConfig.get(propId));
738             }
739         }
740         if (DBG) {
741             Slogf.d(TAG, "getPropertyList returns " + availableProp.size() + " configs");
742         }
743         result.carPropertyConfigList = new CarPropertyConfigList(availableProp);
744         result.missingPermissionPropIds = missingPermissionPropIds.toArray();
745         result.unsupportedPropIds = unsupportedPropIds.toArray();
746         return result;
747     }
748 
749     @Nullable
runSyncOperationCheckLimit(Callable<V> c)750     private <V> V runSyncOperationCheckLimit(Callable<V> c) {
751         synchronized (mLock) {
752             if (mSyncGetSetPropertyOpCount >= SYNC_GET_SET_PROPERTY_OP_LIMIT) {
753                 mConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
754                 throw new ServiceSpecificException(SYNC_OP_LIMIT_TRY_AGAIN);
755             }
756             mSyncGetSetPropertyOpCount += 1;
757             mConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
758             if (DBG) {
759                 Slogf.d(TAG, "mSyncGetSetPropertyOpCount: %d", mSyncGetSetPropertyOpCount);
760             }
761         }
762         try {
763             Trace.traceBegin(TRACE_TAG, "call sync operation");
764             return c.call();
765         } catch (RuntimeException e) {
766             throw e;
767         } catch (Exception e) {
768             Slogf.e(TAG, e, "catching unexpected exception for getProperty/setProperty");
769             return null;
770         } finally {
771             Trace.traceEnd(TRACE_TAG);
772             synchronized (mLock) {
773                 mSyncGetSetPropertyOpCount -= 1;
774                 if (DBG) {
775                     Slogf.d(TAG, "mSyncGetSetPropertyOpCount: %d", mSyncGetSetPropertyOpCount);
776                 }
777             }
778         }
779     }
780 
781     @Override
getProperty(int propertyId, int areaId)782     public CarPropertyValue getProperty(int propertyId, int areaId)
783             throws IllegalArgumentException, ServiceSpecificException {
784         validateGetParameters(propertyId, areaId);
785         Trace.traceBegin(TRACE_TAG, "CarPropertyValue#getProperty");
786         long currentTimeMs = System.currentTimeMillis();
787         try {
788             return runSyncOperationCheckLimit(() -> {
789                 return mPropertyHalService.getProperty(propertyId, areaId);
790             });
791         } finally {
792             if (DBG) {
793                 Slogf.d(TAG, "Latency of getPropertySync is: %f", (float) (System
794                         .currentTimeMillis() - currentTimeMs));
795             }
796             mGetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
797                     - currentTimeMs));
798             Trace.traceEnd(TRACE_TAG);
799         }
800     }
801 
802     /**
803      * Get property value for car service's internal usage.
804      *
805      * @return null if property is not implemented or there is an exception in the vehicle.
806      */
807     @Nullable
getPropertySafe(int propertyId, int areaId)808     public CarPropertyValue getPropertySafe(int propertyId, int areaId) {
809         try {
810             return getProperty(propertyId, areaId);
811         } catch (Exception e) {
812             Slogf.w(TAG, e, "getPropertySafe() failed for property id: %s area id: 0x%s",
813                     VehiclePropertyIds.toString(propertyId), toHexString(areaId));
814             return null;
815         }
816     }
817 
818     /**
819      * Return read permission string for given property ID. The format of the return value of this
820      * function has changed over time and thus should not be relied on.
821      *
822      * @param propId the property ID to query
823      * @return the permission needed to read this property, {@code null} if the property ID is not
824      * available
825      */
826     @Nullable
827     @Override
getReadPermission(int propId)828     public String getReadPermission(int propId) {
829         return mPropertyHalService.getReadPermission(propId);
830     }
831 
832     /**
833      * Return write permission string for given property ID. The format of the return value of this
834      * function has changed over time and thus should not be relied on.
835      *
836      * @param propId the property ID to query
837      * @return the permission needed to write this property, {@code null} if the property ID is not
838      * available
839      */
840     @Nullable
841     @Override
getWritePermission(int propId)842     public String getWritePermission(int propId) {
843         return mPropertyHalService.getWritePermission(propId);
844     }
845 
846     @Override
setProperty(CarPropertyValue carPropertyValue, ICarPropertyEventListener iCarPropertyEventListener)847     public void setProperty(CarPropertyValue carPropertyValue,
848             ICarPropertyEventListener iCarPropertyEventListener)
849             throws IllegalArgumentException, ServiceSpecificException {
850         requireNonNull(iCarPropertyEventListener);
851         validateSetParameters(carPropertyValue);
852         long currentTimeMs = System.currentTimeMillis();
853 
854         runSyncOperationCheckLimit(() -> {
855             mPropertyHalService.setProperty(carPropertyValue);
856             return null;
857         });
858 
859         IBinder listenerBinder = iCarPropertyEventListener.asBinder();
860         synchronized (mLock) {
861             CarPropertyServiceClient client = mClientMap.get(listenerBinder);
862             if (client == null) {
863                 client = new CarPropertyServiceClient(iCarPropertyEventListener,
864                         this::unregisterListenerBinderForProps);
865             }
866             if (client.isDead()) {
867                 Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
868                 return;
869             }
870             // Note that here we are not calling addContinuousProperty or addOnChangeProperty
871             // for this client because we will not enable filtering in this client, so no need to
872             // record these filtering information.
873             mClientMap.put(listenerBinder, client);
874             updateSetOperationRecorderLocked(carPropertyValue.getPropertyId(),
875                     carPropertyValue.getAreaId(), client);
876             if (DBG) {
877                 Slogf.d(TAG, "Latency of setPropertySync is: %f", (float) (System
878                         .currentTimeMillis() - currentTimeMs));
879             }
880             mSetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
881                     - currentTimeMs));
882         }
883     }
884 
885     // Updates recorder for set operation.
886     @GuardedBy("mLock")
updateSetOperationRecorderLocked(int propertyId, int areaId, CarPropertyServiceClient client)887     private void updateSetOperationRecorderLocked(int propertyId, int areaId,
888             CarPropertyServiceClient client) {
889         if (mSetOpClientByAreaIdByPropId.get(propertyId) != null) {
890             mSetOpClientByAreaIdByPropId.get(propertyId).put(areaId, client);
891         } else {
892             SparseArray<CarPropertyServiceClient> areaIdToClient = new SparseArray<>();
893             areaIdToClient.put(areaId, client);
894             mSetOpClientByAreaIdByPropId.put(propertyId, areaIdToClient);
895         }
896     }
897 
898     // Clears map when client unregister for property.
899     @GuardedBy("mLock")
clearSetOperationRecorderLocked(ArraySet<Integer> propertyIds, CarPropertyServiceClient client)900     private void clearSetOperationRecorderLocked(ArraySet<Integer> propertyIds,
901             CarPropertyServiceClient client) {
902         for (int i = 0; i < propertyIds.size(); i++) {
903             int propertyId = propertyIds.valueAt(i);
904             SparseArray<CarPropertyServiceClient> areaIdToClient = mSetOpClientByAreaIdByPropId.get(
905                     propertyId);
906             if (areaIdToClient == null) {
907                 continue;
908             }
909             List<Integer> areaIdsToRemove = new ArrayList<>();
910             for (int j = 0; j < areaIdToClient.size(); j++) {
911                 if (client.equals(areaIdToClient.valueAt(j))) {
912                     areaIdsToRemove.add(areaIdToClient.keyAt(j));
913                 }
914             }
915             for (int j = 0; j < areaIdsToRemove.size(); j++) {
916                 if (DBG) {
917                     Slogf.d(TAG, "clear set operation client for property: %s, area ID: %d",
918                             VehiclePropertyIds.toString(propertyId), areaIdsToRemove.get(j));
919                 }
920                 areaIdToClient.remove(areaIdsToRemove.get(j));
921             }
922             if (areaIdToClient.size() == 0) {
923                 mSetOpClientByAreaIdByPropId.remove(propertyId);
924             }
925         }
926     }
927 
928     // Implement PropertyHalListener interface
929     @Override
onPropertyChange(List<CarPropertyEvent> events)930     public void onPropertyChange(List<CarPropertyEvent> events) {
931         Map<CarPropertyServiceClient, List<CarPropertyEvent>> eventsToDispatch = new ArrayMap<>();
932         synchronized (mLock) {
933             for (int i = 0; i < events.size(); i++) {
934                 CarPropertyEvent event = events.get(i);
935                 int propId = event.getCarPropertyValue().getPropertyId();
936                 int areaId = event.getCarPropertyValue().getAreaId();
937                 Set<CarPropertyServiceClient> clients = mSubscriptionManager.getClients(
938                         propId, areaId);
939                 if (clients == null) {
940                     Slogf.e(TAG,
941                             "onPropertyChange: no listener registered for propId=%s, areaId=%d",
942                             VehiclePropertyIds.toString(propId), areaId);
943                     continue;
944                 }
945 
946                 for (CarPropertyServiceClient client : clients) {
947                     List<CarPropertyEvent> eventsForClient = eventsToDispatch.get(client);
948                     if (eventsForClient == null) {
949                         eventsToDispatch.put(client, new ArrayList<CarPropertyEvent>());
950                     }
951                     eventsToDispatch.get(client).add(event);
952                 }
953             }
954         }
955 
956         // Parse the dispatch list to send events. We must call the callback outside the
957         // scoped lock since the callback might call some function in CarPropertyService
958         // which might cause deadlock.
959         // In rare cases, if this specific client is unregistered after the lock but before
960         // the callback, we would call callback on an unregistered client which should be ok because
961         // 'onEvent' is an async oneway callback that might be delivered after unregistration
962         // anyway.
963         for (CarPropertyServiceClient client : eventsToDispatch.keySet()) {
964             try {
965                 client.onEvent(eventsToDispatch.get(client));
966             } catch (RemoteException ex) {
967                 // If we cannot send a record, it's likely the connection snapped. Let binder
968                 // death handle the situation.
969                 Slogf.e(TAG, "onEvent calling failed: " + ex);
970             }
971         }
972     }
973 
974     @Override
onPropertySetError(int property, int areaId, int errorCode)975     public void onPropertySetError(int property, int areaId, int errorCode) {
976         CarPropertyServiceClient lastOperatedClient = null;
977         synchronized (mLock) {
978             if (mSetOpClientByAreaIdByPropId.get(property) != null
979                     && mSetOpClientByAreaIdByPropId.get(property).get(areaId) != null) {
980                 lastOperatedClient = mSetOpClientByAreaIdByPropId.get(property).get(areaId);
981             } else {
982                 Slogf.e(TAG, "Can not find the client changed propertyId: 0x"
983                         + toHexString(property) + " in areaId: 0x" + toHexString(areaId));
984             }
985 
986         }
987         if (lastOperatedClient != null) {
988             try {
989                 List<CarPropertyEvent> eventList = new ArrayList<>();
990                 eventList.add(
991                         CarPropertyEvent.createErrorEventWithErrorCode(property, areaId,
992                                 errorCode));
993                 // We want all the error events to be delivered to this client with no filtering.
994                 lastOperatedClient.onFilteredEvents(eventList);
995             } catch (RemoteException ex) {
996                 Slogf.e(TAG, "onFilteredEvents calling failed: " + ex);
997             }
998         }
999     }
1000 
validateGetSetAsyncParameters(AsyncPropertyServiceRequestList requests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1001     private static void validateGetSetAsyncParameters(AsyncPropertyServiceRequestList requests,
1002             IAsyncPropertyResultCallback asyncPropertyResultCallback,
1003             long timeoutInMs) throws IllegalArgumentException {
1004         requireNonNull(requests);
1005         requireNonNull(asyncPropertyResultCallback);
1006         Preconditions.checkArgument(timeoutInMs > 0, "timeoutInMs must be a positive number");
1007     }
1008 
1009     /**
1010      * Gets CarPropertyValues asynchronously.
1011      */
1012     @Override
getPropertiesAsync( AsyncPropertyServiceRequestList getPropertyServiceRequestsParcelable, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1013     public void getPropertiesAsync(
1014             AsyncPropertyServiceRequestList getPropertyServiceRequestsParcelable,
1015             IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs) {
1016         validateGetSetAsyncParameters(getPropertyServiceRequestsParcelable,
1017                 asyncPropertyResultCallback, timeoutInMs);
1018         long currentTime = System.currentTimeMillis();
1019         List<AsyncPropertyServiceRequest> getPropertyServiceRequests =
1020                 getPropertyServiceRequestsParcelable.getList();
1021         for (int i = 0; i < getPropertyServiceRequests.size(); i++) {
1022             validateGetParameters(getPropertyServiceRequests.get(i).getPropertyId(),
1023                     getPropertyServiceRequests.get(i).getAreaId());
1024         }
1025         mPropertyHalService.getCarPropertyValuesAsync(getPropertyServiceRequests,
1026                 asyncPropertyResultCallback, timeoutInMs, currentTime);
1027         if (DBG) {
1028             Slogf.d(TAG, "Latency of getPropertyAsync is: %f", (float) (System
1029                     .currentTimeMillis() - currentTime));
1030         }
1031         mGetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
1032     }
1033 
1034     /**
1035      * Sets CarPropertyValues asynchronously.
1036      */
1037     @SuppressWarnings("FormatString")
1038     @Override
setPropertiesAsync(AsyncPropertyServiceRequestList setPropertyServiceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)1039     public void setPropertiesAsync(AsyncPropertyServiceRequestList setPropertyServiceRequests,
1040             IAsyncPropertyResultCallback asyncPropertyResultCallback,
1041             long timeoutInMs) {
1042         validateGetSetAsyncParameters(setPropertyServiceRequests, asyncPropertyResultCallback,
1043                 timeoutInMs);
1044         long currentTime = System.currentTimeMillis();
1045         List<AsyncPropertyServiceRequest> setPropertyServiceRequestList =
1046                 setPropertyServiceRequests.getList();
1047         for (int i = 0; i < setPropertyServiceRequestList.size(); i++) {
1048             AsyncPropertyServiceRequest request = setPropertyServiceRequestList.get(i);
1049             CarPropertyValue carPropertyValueToSet = request.getCarPropertyValue();
1050             int propertyId = request.getPropertyId();
1051             int valuePropertyId = carPropertyValueToSet.getPropertyId();
1052             int areaId = request.getAreaId();
1053             int valueAreaId = carPropertyValueToSet.getAreaId();
1054             String propertyName = VehiclePropertyIds.toString(propertyId);
1055             if (valuePropertyId != propertyId) {
1056                 throw new IllegalArgumentException(String.format(
1057                         "Property ID in request and CarPropertyValue mismatch: %s vs %s",
1058                         VehiclePropertyIds.toString(valuePropertyId), propertyName).toString());
1059             }
1060             if (valueAreaId != areaId) {
1061                 throw new IllegalArgumentException(String.format(
1062                         "For property: %s, area ID in request and CarPropertyValue mismatch: %d vs"
1063                         + " %d", propertyName, valueAreaId, areaId).toString());
1064             }
1065             validateSetParameters(carPropertyValueToSet);
1066             if (request.isWaitForPropertyUpdate()) {
1067                 if (NOT_ALLOWED_WAIT_FOR_UPDATE_PROPERTIES.contains(propertyId)) {
1068                     throw new IllegalArgumentException("Property: "
1069                             + propertyName + " must set waitForPropertyUpdate to false");
1070                 }
1071                 validateGetParameters(propertyId, areaId);
1072             }
1073         }
1074         mPropertyHalService.setCarPropertyValuesAsync(setPropertyServiceRequestList,
1075                 asyncPropertyResultCallback, timeoutInMs, currentTime);
1076         if (DBG) {
1077             Slogf.d(TAG, "Latency of setPropertyAsync is: %f", (float) (System
1078                     .currentTimeMillis() - currentTime));
1079         }
1080         mSetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
1081     }
1082 
1083     @Override
getSupportedNoReadPermPropIds(int[] propertyIds)1084     public int[] getSupportedNoReadPermPropIds(int[] propertyIds) {
1085         List<Integer> noReadPermPropertyIds = new ArrayList<>();
1086         for (int propertyId : propertyIds) {
1087             if (getCarPropertyConfig(propertyId) == null) {
1088                 // Not supported
1089                 continue;
1090             }
1091             if (!mPropertyHalService.isReadable(mContext, propertyId)) {
1092                 noReadPermPropertyIds.add(propertyId);
1093             }
1094         }
1095         return ArrayUtils.convertToIntArray(noReadPermPropertyIds);
1096     }
1097 
1098     @Override
isSupportedAndHasWritePermissionOnly(int propertyId)1099     public boolean isSupportedAndHasWritePermissionOnly(int propertyId) {
1100         return getCarPropertyConfig(propertyId) != null
1101                 && mPropertyHalService.isWritable(mContext, propertyId)
1102                 && !mPropertyHalService.isReadable(mContext, propertyId);
1103     }
1104 
1105     /**
1106      * Cancel on-going async requests.
1107      *
1108      * @param serviceRequestIds A list of async get/set property request IDs.
1109      */
1110     @Override
cancelRequests(int[] serviceRequestIds)1111     public void cancelRequests(int[] serviceRequestIds) {
1112         mPropertyHalService.cancelRequests(serviceRequestIds);
1113     }
1114 
assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig, int areaId)1115     private void assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig,
1116             int areaId) {
1117         int accessLevel = mFeatureFlags.areaIdConfigAccess()
1118                 ? carPropertyConfig.getAreaIdConfig(areaId).getAccess()
1119                 : carPropertyConfig.getAccess();
1120         Preconditions.checkArgument(
1121                 accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ
1122                         || accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
1123                 "Property: %s is not readable at areaId: %d",
1124                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()), areaId);
1125     }
1126 
assertAreaIdIsSupported(CarPropertyConfig<?> carPropertyConfig, int areaId)1127     private static void assertAreaIdIsSupported(CarPropertyConfig<?> carPropertyConfig,
1128             int areaId) {
1129         Preconditions.checkArgument(ArrayUtils.contains(carPropertyConfig.getAreaIds(), areaId),
1130                 "area ID: 0x" + toHexString(areaId) + " not supported for property ID: "
1131                         + VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()));
1132     }
1133 
initializeHistogram()1134     private void initializeHistogram() {
1135         mConcurrentSyncOperationHistogram = mHistogramFactory.newUniformHistogram(
1136                 "automotive_os.value_concurrent_sync_operations",
1137                 /* binCount= */ 17, /* minValue= */ 0, /* exclusiveMaxValue= */ 17);
1138         mGetPropertySyncLatencyHistogram = mHistogramFactory.newScaledRangeHistogram(
1139                 "automotive_os.value_sync_get_property_latency",
1140                 /* binCount= */ 20, /* minValue= */ 0,
1141                 /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f);
1142         mSetPropertySyncLatencyHistogram = mHistogramFactory.newScaledRangeHistogram(
1143                 "automotive_os.value_sync_set_property_latency",
1144                 /* binCount= */ 20, /* minValue= */ 0,
1145                 /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f);
1146         mSubscriptionUpdateRateHistogram = mHistogramFactory.newUniformHistogram(
1147                 "automotive_os.value_subscription_update_rate",
1148                 /* binCount= */ 101, /* minValue= */ 0, /* exclusiveMaxValue= */ 101);
1149         mGetAsyncLatencyHistogram = mHistogramFactory.newUniformHistogram(
1150                 "automotive_os.value_get_async_latency",
1151                 /* binCount= */ 20, /* minValue= */ 0, /* exclusiveMaxValue= */ 1000);
1152         mSetAsyncLatencyHistogram = mHistogramFactory.newUniformHistogram(
1153                 "automotive_os.value_set_async_latency",
1154                 /* binCount= */ 20, /* minValue= */ 0, /* exclusiveMaxValue= */ 1000);
1155     }
1156 
1157     @Nullable
getCarPropertyConfig(int propertyId)1158     private CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
1159         CarPropertyConfig<?> carPropertyConfig;
1160         synchronized (mLock) {
1161             carPropertyConfig = mPropertyIdToCarPropertyConfig.get(propertyId);
1162         }
1163         return carPropertyConfig;
1164     }
1165 
assertReadPermissionGranted(int propertyId)1166     private void assertReadPermissionGranted(int propertyId) {
1167         if (!mPropertyHalService.isReadable(mContext, propertyId)) {
1168             throw new SecurityException(
1169                     "Platform does not have permission to read value for property ID: "
1170                             + VehiclePropertyIds.toString(propertyId));
1171         }
1172     }
1173 
assertConfigNotNullAndGetConfig(int propertyId)1174     private CarPropertyConfig assertConfigNotNullAndGetConfig(int propertyId) {
1175         CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId);
1176         Preconditions.checkArgument(carPropertyConfig != null,
1177                 "property ID is not in carPropertyConfig list, and so it is not supported: %s",
1178                 VehiclePropertyIds.toString(propertyId));
1179         return carPropertyConfig;
1180     }
1181 
assertIfReadableAtAreaIds(CarPropertyConfig<?> carPropertyConfig, int[] areaIds)1182     private void assertIfReadableAtAreaIds(CarPropertyConfig<?> carPropertyConfig, int[] areaIds) {
1183         for (int i = 0; i < areaIds.length; i++) {
1184             assertAreaIdIsSupported(carPropertyConfig, areaIds[i]);
1185             assertPropertyIsReadable(carPropertyConfig, areaIds[i]);
1186         }
1187         assertReadPermissionGranted(carPropertyConfig.getPropertyId());
1188     }
1189 
validateRegisterParameterAndGetConfig(int propertyId, int[] areaIds)1190     private CarPropertyConfig validateRegisterParameterAndGetConfig(int propertyId,
1191             int[] areaIds) {
1192         CarPropertyConfig<?> carPropertyConfig = assertConfigNotNullAndGetConfig(propertyId);
1193         Preconditions.checkArgument(areaIds != null, "AreaIds must not be null");
1194         Preconditions.checkArgument(areaIds.length != 0, "AreaIds must not be empty");
1195         assertIfReadableAtAreaIds(carPropertyConfig, areaIds);
1196         return carPropertyConfig;
1197     }
1198 
validateGetParameters(int propertyId, int areaId)1199     private void validateGetParameters(int propertyId, int areaId) {
1200         CarPropertyConfig<?> carPropertyConfig = assertConfigNotNullAndGetConfig(propertyId);
1201         assertAreaIdIsSupported(carPropertyConfig, areaId);
1202         assertPropertyIsReadable(carPropertyConfig, areaId);
1203         assertReadPermissionGranted(propertyId);
1204     }
1205 
validateSetParameters(CarPropertyValue<?> carPropertyValue)1206     private void validateSetParameters(CarPropertyValue<?> carPropertyValue) {
1207         requireNonNull(carPropertyValue);
1208         int propertyId = carPropertyValue.getPropertyId();
1209         int areaId = carPropertyValue.getAreaId();
1210         Object valueToSet = carPropertyValue.getValue();
1211         CarPropertyConfig<?> carPropertyConfig = assertConfigNotNullAndGetConfig(propertyId);
1212         assertAreaIdIsSupported(carPropertyConfig, areaId);
1213 
1214         // Assert property is writable.
1215         int accessLevel = mFeatureFlags.areaIdConfigAccess()
1216                 ? carPropertyConfig.getAreaIdConfig(areaId).getAccess()
1217                 : carPropertyConfig.getAccess();
1218         Preconditions.checkArgument(
1219                 accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE
1220                         || accessLevel == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
1221                 "Property: %s is not writable at areaId: %d",
1222                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()), areaId);
1223 
1224         // Assert write permission is granted.
1225         if (!mPropertyHalService.isWritable(mContext, propertyId)) {
1226             throw new SecurityException(
1227                     "Platform does not have permission to write value for property ID: "
1228                             + VehiclePropertyIds.toString(propertyId));
1229         }
1230 
1231         // Assert set value is valid for property.
1232         Preconditions.checkArgument(valueToSet != null,
1233                 "setProperty: CarPropertyValue's must not be null - property ID: %s area ID: %s",
1234                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1235                 toHexString(areaId));
1236         Preconditions.checkArgument(
1237                 valueToSet.getClass().equals(carPropertyConfig.getPropertyType()),
1238                 "setProperty: CarPropertyValue's value's type does not match property's type. - "
1239                         + "property ID: %s area ID: %s",
1240                 VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1241                 toHexString(areaId));
1242 
1243         AreaIdConfig<?> areaIdConfig = carPropertyConfig.getAreaIdConfig(areaId);
1244         if (areaIdConfig.getMinValue() != null) {
1245             boolean isGreaterThanOrEqualToMinValue = false;
1246             if (carPropertyConfig.getPropertyType().equals(Integer.class)) {
1247                 isGreaterThanOrEqualToMinValue =
1248                         (Integer) valueToSet >= (Integer) areaIdConfig.getMinValue();
1249             } else if (carPropertyConfig.getPropertyType().equals(Long.class)) {
1250                 isGreaterThanOrEqualToMinValue =
1251                         (Long) valueToSet >= (Long) areaIdConfig.getMinValue();
1252             } else if (carPropertyConfig.getPropertyType().equals(Float.class)) {
1253                 isGreaterThanOrEqualToMinValue =
1254                         (Float) valueToSet >= (Float) areaIdConfig.getMinValue();
1255             }
1256             Preconditions.checkArgument(isGreaterThanOrEqualToMinValue,
1257                     "setProperty: value to set must be greater than or equal to the area ID min "
1258                             + "value. - " + "property ID: %s area ID: 0x%s min value: %s",
1259                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1260                     toHexString(areaId), areaIdConfig.getMinValue());
1261 
1262         }
1263 
1264         if (areaIdConfig.getMaxValue() != null) {
1265             boolean isLessThanOrEqualToMaxValue = false;
1266             if (carPropertyConfig.getPropertyType().equals(Integer.class)) {
1267                 isLessThanOrEqualToMaxValue =
1268                         (Integer) valueToSet <= (Integer) areaIdConfig.getMaxValue();
1269             } else if (carPropertyConfig.getPropertyType().equals(Long.class)) {
1270                 isLessThanOrEqualToMaxValue =
1271                         (Long) valueToSet <= (Long) areaIdConfig.getMaxValue();
1272             } else if (carPropertyConfig.getPropertyType().equals(Float.class)) {
1273                 isLessThanOrEqualToMaxValue =
1274                         (Float) valueToSet <= (Float) areaIdConfig.getMaxValue();
1275             }
1276             Preconditions.checkArgument(isLessThanOrEqualToMaxValue,
1277                     "setProperty: value to set must be less than or equal to the area ID max "
1278                             + "value. - " + "property ID: %s area ID: 0x%s min value: %s",
1279                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1280                     toHexString(areaId), areaIdConfig.getMaxValue());
1281 
1282         }
1283 
1284         if (!areaIdConfig.getSupportedEnumValues().isEmpty()) {
1285             Preconditions.checkArgument(areaIdConfig.getSupportedEnumValues().contains(valueToSet),
1286                     "setProperty: value to set must exist in set of supported enum values. - "
1287                             + "property ID: %s area ID: 0x%s supported enum values: %s",
1288                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1289                     toHexString(areaId), areaIdConfig.getSupportedEnumValues());
1290         }
1291 
1292         if (PROPERTY_ID_TO_UNWRITABLE_STATES.contains(carPropertyConfig.getPropertyId())) {
1293             Preconditions.checkArgument(!(PROPERTY_ID_TO_UNWRITABLE_STATES
1294                     .get(carPropertyConfig.getPropertyId()).contains(valueToSet)),
1295                     "setProperty: value to set: %s must not be an unwritable state value. - "
1296                             + "property ID: %s area ID: 0x%s unwritable states: %s",
1297                     valueToSet,
1298                     VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()),
1299                     toHexString(areaId),
1300                     PROPERTY_ID_TO_UNWRITABLE_STATES.get(carPropertyConfig.getPropertyId()));
1301         }
1302     }
1303 }
1304