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