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 android.car.hardware.property;
18 
19 import static java.lang.Integer.toHexString;
20 
21 import android.annotation.FloatRange;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.car.Car;
26 import android.car.CarManagerBase;
27 import android.car.VehicleAreaType;
28 import android.car.VehiclePropertyIds;
29 import android.car.hardware.CarPropertyConfig;
30 import android.car.hardware.CarPropertyValue;
31 import android.os.Build;
32 import android.os.Handler;
33 import android.os.RemoteException;
34 import android.os.ServiceSpecificException;
35 import android.util.ArraySet;
36 import android.util.Log;
37 import android.util.SparseArray;
38 
39 import com.android.car.internal.CarRatedFloatListeners;
40 import com.android.car.internal.SingleMessageHandler;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.lang.ref.WeakReference;
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.function.Consumer;
48 
49 
50 /**
51  * Provides an application interface for interacting with the Vehicle specific properties.
52  * For details about the individual properties, see the descriptions in
53  * hardware/interfaces/automotive/vehicle/types.hal
54  */
55 public class CarPropertyManager extends CarManagerBase {
56     private static final boolean DBG = false;
57     private static final String TAG = "CarPropertyManager";
58     private static final int MSG_GENERIC_EVENT = 0;
59     private final SingleMessageHandler<CarPropertyEvent> mHandler;
60     private final ICarProperty mService;
61     private final int mAppTargetSdk;
62 
63     private CarPropertyEventListenerToService mCarPropertyEventToService;
64 
65     /** Record of locally active properties. Key is propertyId */
66     private final SparseArray<CarPropertyListeners> mActivePropertyListener =
67             new SparseArray<>();
68     /** Record of properties' configs. Key is propertyId */
69     private final SparseArray<CarPropertyConfig> mConfigMap = new SparseArray<>();
70 
71     /**
72      * Application registers {@link CarPropertyEventCallback} object to receive updates and changes
73      * to subscribed Vehicle specific properties.
74      */
75     public interface CarPropertyEventCallback {
76         /**
77          * Called when a property is updated
78          * @param value Property that has been updated.
79          */
onChangeEvent(CarPropertyValue value)80         void onChangeEvent(CarPropertyValue value);
81 
82         /**
83          * Called when an error is detected when setting a property.
84          *
85          * @param propId Property ID which is detected an error.
86          * @param zone Zone which is detected an error.
87          *
88          * @see CarPropertyEventCallback#onErrorEvent(int, int, int)
89          */
onErrorEvent(int propId, int zone)90         void onErrorEvent(int propId, int zone);
91 
92         /**
93          * Called when an error is detected when setting a property.
94          *
95          * <p>Clients which changed the property value in the areaId most recently will receive
96          * this callback. If multiple clients set a property for the same area id simultaneously,
97          * which one takes precedence is undefined. Typically, the last set operation
98          * (in the order that they are issued to car's ECU) overrides the previous set operations.
99          * The delivered error reflects the error happened in the last set operation.
100          *
101          * @param propId Property ID which is detected an error.
102          * @param areaId AreaId which is detected an error.
103          * @param errorCode Error code is raised in the car.
104          */
onErrorEvent(int propId, int areaId, @CarSetPropertyErrorCode int errorCode)105         default void onErrorEvent(int propId, int areaId, @CarSetPropertyErrorCode int errorCode) {
106             if (DBG) {
107                 Log.d(TAG, "onErrorEvent propertyId: 0x" + toHexString(propId) + " areaId:0x"
108                         + toHexString(areaId) + " ErrorCode: " + errorCode);
109             }
110             onErrorEvent(propId, areaId);
111         }
112     }
113 
114     /** Read ON_CHANGE sensors */
115     public static final float SENSOR_RATE_ONCHANGE = 0f;
116     /** Read sensors at the rate of  1 hertz */
117     public static final float SENSOR_RATE_NORMAL = 1f;
118     /** Read sensors at the rate of 5 hertz */
119     public static final float SENSOR_RATE_UI = 5f;
120     /** Read sensors at the rate of 10 hertz */
121     public static final float SENSOR_RATE_FAST = 10f;
122     /** Read sensors at the rate of 100 hertz */
123     public static final float SENSOR_RATE_FASTEST = 100f;
124 
125 
126 
127     /**
128      * Status to indicate that set operation failed. Try it again.
129      */
130     public static final int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN = 1;
131 
132     /**
133      * Status to indicate that set operation failed because of an invalid argument.
134      */
135     public static final int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG = 2;
136 
137     /**
138      * Status to indicate that set operation failed because the property is not available.
139      */
140     public static final int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE = 3;
141 
142     /**
143      * Status to indicate that set operation failed because car denied access to the property.
144      */
145     public static final int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED = 4;
146 
147     /**
148      * Status to indicate that set operation failed because of an general error in cars.
149      */
150     public static final int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN = 5;
151 
152     /** @hide */
153     @IntDef(prefix = {"CAR_SET_PROPERTY_ERROR_CODE_"}, value = {
154             CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN,
155             CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG,
156             CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE,
157             CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED,
158             CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN,
159     })
160     @Retention(RetentionPolicy.SOURCE)
161     public @interface CarSetPropertyErrorCode {}
162 
163     /**
164      * Get an instance of the CarPropertyManager.
165      *
166      * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
167      * @param car Car instance
168      * @param service ICarProperty instance
169      * @hide
170      */
CarPropertyManager(Car car, @NonNull ICarProperty service)171     public CarPropertyManager(Car car, @NonNull ICarProperty service) {
172         super(car);
173         mService = service;
174         mAppTargetSdk = getContext().getApplicationInfo().targetSdkVersion;
175         try {
176             List<CarPropertyConfig> configs = mService.getPropertyList();
177             for (CarPropertyConfig carPropertyConfig : configs) {
178                 mConfigMap.put(carPropertyConfig.getPropertyId(), carPropertyConfig);
179             }
180         } catch (RemoteException e) {
181             Log.e(TAG, "getPropertyList exception ", e);
182             throw new RuntimeException(e);
183         }
184 
185         Handler eventHandler = getEventHandler();
186         if (eventHandler == null) {
187             mHandler = null;
188             return;
189         }
190         mHandler = new SingleMessageHandler<CarPropertyEvent>(eventHandler.getLooper(),
191             MSG_GENERIC_EVENT) {
192             @Override
193             protected void handleEvent(CarPropertyEvent event) {
194                 CarPropertyListeners listeners;
195                 synchronized (mActivePropertyListener) {
196                     listeners = mActivePropertyListener.get(
197                         event.getCarPropertyValue().getPropertyId());
198                 }
199                 if (listeners != null) {
200                     switch (event.getEventType()) {
201                         case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE:
202                             listeners.onPropertyChanged(event);
203                             break;
204                         case CarPropertyEvent.PROPERTY_EVENT_ERROR:
205                             listeners.onErrorEvent(event);
206                             break;
207                         default:
208                             throw new IllegalArgumentException();
209                     }
210                 }
211             }
212         };
213     }
214 
215     /**
216      * Register {@link CarPropertyEventCallback} to get property updates. Multiple listeners
217      * can be registered for a single property or the same listener can be used for different
218      * properties. If the same listener is registered again for the same property, it will be
219      * updated to new rate.
220      * <p>Rate could be one of the following:
221      * <ul>
222      *   <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li>
223      *   <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li>
224      *   <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li>
225      *   <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li>
226      *   <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li>
227      * </ul>
228      * <p>
229      * <b>Note:</b>Rate has no effect if the property has one of the following change modes:
230      * <ul>
231      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li>
232      *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
233      * </ul>
234      * See {@link CarPropertyConfig#getChangeMode()} for details.
235      * If rate is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it will be registered
236      * with max sample rate.
237      * If rate is lower than {@link CarPropertyConfig#getMinSampleRate()}, it will be registered
238      * with min sample rate.
239      *
240      * @param callback CarPropertyEventCallback to be registered.
241      * @param propertyId PropertyId to subscribe
242      * @param rate how fast the property events are delivered in Hz.
243      * @return true if the listener is successfully registered.
244      * @throws SecurityException if missing the appropriate permission.
245      */
registerCallback(@onNull CarPropertyEventCallback callback, int propertyId, @FloatRange(from = 0.0, to = 100.0) float rate)246     public boolean registerCallback(@NonNull CarPropertyEventCallback callback,
247             int propertyId, @FloatRange(from = 0.0, to = 100.0) float rate) {
248         synchronized (mActivePropertyListener) {
249             if (mCarPropertyEventToService == null) {
250                 mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
251             }
252             CarPropertyConfig config = mConfigMap.get(propertyId);
253             if (config == null) {
254                 Log.e(TAG, "registerListener:  propId is not in config list:  " + propertyId);
255                 return false;
256             }
257             if (config.getChangeMode() == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
258                 rate = SENSOR_RATE_ONCHANGE;
259             }
260             boolean needsServerUpdate = false;
261             CarPropertyListeners listeners;
262             listeners = mActivePropertyListener.get(propertyId);
263             if (listeners == null) {
264                 listeners = new CarPropertyListeners(rate);
265                 mActivePropertyListener.put(propertyId, listeners);
266                 needsServerUpdate = true;
267             }
268             if (listeners.addAndUpdateRate(callback, rate)) {
269                 needsServerUpdate = true;
270             }
271             if (needsServerUpdate) {
272                 if (!registerOrUpdatePropertyListener(propertyId, rate)) {
273                     return false;
274                 }
275             }
276         }
277         return true;
278     }
279 
registerOrUpdatePropertyListener(int propertyId, float rate)280     private boolean registerOrUpdatePropertyListener(int propertyId, float rate) {
281         try {
282             mService.registerListener(propertyId, rate, mCarPropertyEventToService);
283         } catch (RemoteException e) {
284             return handleRemoteExceptionFromCarService(e, false);
285         }
286         return true;
287     }
288 
289     private static class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub{
290         private final WeakReference<CarPropertyManager> mMgr;
291 
CarPropertyEventListenerToService(CarPropertyManager mgr)292         CarPropertyEventListenerToService(CarPropertyManager mgr) {
293             mMgr = new WeakReference<>(mgr);
294         }
295 
296         @Override
onEvent(List<CarPropertyEvent> events)297         public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
298             CarPropertyManager manager = mMgr.get();
299             if (manager != null) {
300                 manager.handleEvent(events);
301             }
302         }
303     }
304 
handleEvent(List<CarPropertyEvent> events)305     private void handleEvent(List<CarPropertyEvent> events) {
306         if (mHandler != null) {
307             mHandler.sendEvents(events);
308         }
309     }
310 
311     /**
312      * Stop getting property update for the given callback. If there are multiple registrations for
313      * this callback, all listening will be stopped.
314      * @param callback CarPropertyEventCallback to be unregistered.
315      */
unregisterCallback(@onNull CarPropertyEventCallback callback)316     public void unregisterCallback(@NonNull CarPropertyEventCallback callback) {
317         synchronized (mActivePropertyListener) {
318             int [] propertyIds = new int[mActivePropertyListener.size()];
319             for (int i = 0; i < mActivePropertyListener.size(); i++) {
320                 propertyIds[i] = mActivePropertyListener.keyAt(i);
321             }
322             for (int prop : propertyIds) {
323                 doUnregisterListenerLocked(callback, prop);
324             }
325         }
326     }
327 
328     /**
329      * Stop getting property update for the given callback and property. If the same callback is
330      * used for other properties, those subscriptions will not be affected.
331      *
332      * @param callback CarPropertyEventCallback to be unregistered.
333      * @param propertyId PropertyId to be unregistered.
334      */
unregisterCallback(@onNull CarPropertyEventCallback callback, int propertyId)335     public void unregisterCallback(@NonNull CarPropertyEventCallback callback, int propertyId) {
336         synchronized (mActivePropertyListener) {
337             doUnregisterListenerLocked(callback, propertyId);
338         }
339     }
340 
doUnregisterListenerLocked(CarPropertyEventCallback listener, int propertyId)341     private void doUnregisterListenerLocked(CarPropertyEventCallback listener, int propertyId) {
342         CarPropertyListeners listeners = mActivePropertyListener.get(propertyId);
343         if (listeners != null) {
344             boolean needsServerUpdate = false;
345             if (listeners.contains(listener)) {
346                 needsServerUpdate = listeners.remove(listener);
347             }
348             if (listeners.isEmpty()) {
349                 try {
350                     mService.unregisterListener(propertyId, mCarPropertyEventToService);
351                 } catch (RemoteException e) {
352                     handleRemoteExceptionFromCarService(e);
353                     // continue for local clean-up
354                 }
355                 mActivePropertyListener.remove(propertyId);
356             } else if (needsServerUpdate) {
357                 registerOrUpdatePropertyListener(propertyId, listeners.getRate());
358             }
359         }
360     }
361 
362     /**
363      * @return List of properties implemented by this car that the application may access.
364      */
365     @NonNull
getPropertyList()366     public List<CarPropertyConfig> getPropertyList() {
367         List<CarPropertyConfig> configs = new ArrayList<>(mConfigMap.size());
368         for (int i = 0; i < mConfigMap.size(); i++) {
369             configs.add(mConfigMap.valueAt(i));
370         }
371         return configs;
372     }
373 
374     /**
375      * @param propertyIds property ID list
376      * @return List of properties implemented by this car in given property ID list that application
377      *          may access.
378      */
379     @NonNull
getPropertyList(@onNull ArraySet<Integer> propertyIds)380     public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) {
381         List<CarPropertyConfig> configs = new ArrayList<>();
382         for (int propId : propertyIds) {
383             checkSupportedProperty(propId);
384             CarPropertyConfig config = mConfigMap.get(propId);
385             if (config != null) {
386                 configs.add(config);
387             }
388         }
389         return configs;
390     }
391 
392     /**
393      * Get CarPropertyConfig by property Id.
394      *
395      * @param propId Property ID
396      * @return {@link CarPropertyConfig} for the selected property.
397      * Null if the property is not available.
398      */
399     @Nullable
getCarPropertyConfig(int propId)400     public CarPropertyConfig<?> getCarPropertyConfig(int propId) {
401         checkSupportedProperty(propId);
402 
403         return  mConfigMap.get(propId);
404     }
405 
406     /**
407      * Returns areaId contains the seletcted area for the property.
408      *
409      * @param propId Property ID
410      * @param area Area enum such as Enums in {@link android.car.VehicleAreaSeat}.
411      * @throws IllegalArgumentException if the property is not available in the vehicle for
412      * the selected area.
413      * @return AreaId contains the selected area for the property.
414      */
getAreaId(int propId, int area)415     public int getAreaId(int propId, int area) {
416         checkSupportedProperty(propId);
417 
418         CarPropertyConfig<?> propConfig = getCarPropertyConfig(propId);
419         if (propConfig == null) {
420             throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId)
421                     + " is not available");
422         }
423         // For the global property, areaId is 0
424         if (propConfig.isGlobalProperty()) {
425             return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
426         }
427         for (int areaId : propConfig.getAreaIds()) {
428             if ((area & areaId) == area) {
429                 return areaId;
430             }
431         }
432 
433         throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId)
434                 + " is not available at the area: 0x" + toHexString(area));
435     }
436 
437     /**
438      * Return read permission string for given property ID.
439      *
440      * @param propId Property ID to query
441      * @return String Permission needed to read this property.  NULL if propId not available.
442      * @hide
443      */
444     @Nullable
getReadPermission(int propId)445     public String getReadPermission(int propId) {
446         if (DBG) {
447             Log.d(TAG, "getReadPermission, propId: 0x" + toHexString(propId));
448         }
449         checkSupportedProperty(propId);
450         try {
451             return mService.getReadPermission(propId);
452         } catch (RemoteException e) {
453             return handleRemoteExceptionFromCarService(e, "");
454         }
455     }
456 
457     /**
458      * Return write permission string for given property ID.
459      *
460      * @param propId Property ID to query
461      * @return String Permission needed to write this property.  NULL if propId not available.
462      * @hide
463      */
464     @Nullable
getWritePermission(int propId)465     public String getWritePermission(int propId) {
466         if (DBG) {
467             Log.d(TAG, "getWritePermission, propId: 0x" + toHexString(propId));
468         }
469         checkSupportedProperty(propId);
470         try {
471             return mService.getWritePermission(propId);
472         } catch (RemoteException e) {
473             return handleRemoteExceptionFromCarService(e, "");
474         }
475     }
476 
477 
478     /**
479      * Check whether a given property is available or disabled based on the car's current state.
480      * @param propId Property Id
481      * @param area AreaId of property
482      * @return true if STATUS_AVAILABLE, false otherwise (eg STATUS_UNAVAILABLE)
483      */
isPropertyAvailable(int propId, int area)484     public boolean isPropertyAvailable(int propId, int area) {
485         checkSupportedProperty(propId);
486         try {
487             CarPropertyValue propValue = mService.getProperty(propId, area);
488             return (propValue != null)
489                     && (propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE);
490         } catch (RemoteException e) {
491             return handleRemoteExceptionFromCarService(e, false);
492         }
493     }
494 
495     /**
496      * Returns value of a bool property
497      * <p> This method may take couple seconds to complete, so it needs to be called from an
498      * non-main thread.
499      *
500      * @param prop Property ID to get
501      * @param area Area of the property to get
502      * @return value of a bool property.
503      */
getBooleanProperty(int prop, int area)504     public boolean getBooleanProperty(int prop, int area) {
505         checkSupportedProperty(prop);
506         CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area);
507         return carProp != null ? carProp.getValue() : false;
508     }
509 
510     /**
511      * Returns value of a float property
512      *
513      * <p> This method may take couple seconds to complete, so it needs to be called from an
514      * non-main thread.
515      *
516      * @param prop Property ID to get
517      * @param area Area of the property to get
518      */
getFloatProperty(int prop, int area)519     public float getFloatProperty(int prop, int area) {
520         checkSupportedProperty(prop);
521         CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area);
522         return carProp != null ? carProp.getValue() : 0f;
523     }
524 
525     /**
526      * Returns value of a integer property
527      *
528      * <p> This method may take couple seconds to complete, so it needs to be called form an
529      * non-main thread.
530      * @param prop Property ID to get
531      * @param area Zone of the property to get
532      */
getIntProperty(int prop, int area)533     public int getIntProperty(int prop, int area) {
534         checkSupportedProperty(prop);
535         CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
536         return carProp != null ? carProp.getValue() : 0;
537     }
538 
539     /**
540      * Returns value of a integer array property
541      *
542      * <p> This method may take couple seconds to complete, so it needs to be called from an
543      * non-main thread.
544      *
545      * @param prop Property ID to get
546      * @param area Zone of the property to get
547      */
548     @NonNull
getIntArrayProperty(int prop, int area)549     public int[] getIntArrayProperty(int prop, int area) {
550         checkSupportedProperty(prop);
551         CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, prop, area);
552         return carProp != null ? toIntArray(carProp.getValue()) : new int[0];
553     }
554 
toIntArray(Integer[] input)555     private static int[] toIntArray(Integer[] input) {
556         int len = input.length;
557         int[] arr = new int[len];
558         for (int i = 0; i < len; i++) {
559             arr[i] = input[i];
560         }
561         return arr;
562     }
563 
564     /**
565      * Return CarPropertyValue
566      *
567      * <p> This method may take couple seconds to complete, so it needs to be called from an
568      * non-main thread.
569      *
570      * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
571      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
572      * request is failed.
573      * <ul>
574      *     <li>{@link CarInternalErrorException}
575      *     <li>{@link PropertyAccessDeniedSecurityException}
576      *     <li>{@link PropertyNotAvailableAndRetryException}
577      *     <li>{@link PropertyNotAvailableException}
578      *     <li>{@link IllegalArgumentException}
579      * </ul>
580      * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
581      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
582      * is failed.
583      * <ul>
584      *     <li>{@link IllegalStateException} when there is an error detected in cars.
585      *     <li>{@link IllegalArgumentException} when the property in the areaId is not supplied.
586      * </ul>
587      *
588      * @param clazz The class object for the CarPropertyValue
589      * @param propId Property ID to get
590      * @param areaId Zone of the property to get
591      *
592      * @throws {@link CarInternalErrorException} when there is an error detected in cars.
593      * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
594      * property.
595      * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
596      * not available and likely that retrying will be successful.
597      * @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
598      * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
599      *
600      * @return CarPropertyValue. Null if property's id is invalid.
601      */
602     @SuppressWarnings("unchecked")
603     @Nullable
getProperty(@onNull Class<E> clazz, int propId, int areaId)604     public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) {
605         if (DBG) {
606             Log.d(TAG, "getProperty, propId: 0x" + toHexString(propId)
607                     + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz);
608         }
609 
610         checkSupportedProperty(propId);
611 
612         try {
613             CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);
614             if (propVal != null && propVal.getValue() != null) {
615                 Class<?> actualClass = propVal.getValue().getClass();
616                 if (actualClass != clazz) {
617                     throw new IllegalArgumentException("Invalid property type. " + "Expected: "
618                             + clazz + ", but was: " + actualClass);
619                 }
620             }
621             return propVal;
622         } catch (RemoteException e) {
623             return handleRemoteExceptionFromCarService(e, null);
624         } catch (ServiceSpecificException e) {
625             // For pre R apps, throws the old exceptions.
626             if (mAppTargetSdk < Build.VERSION_CODES.R) {
627                 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
628                     return null;
629                 } else {
630                     throw new IllegalStateException(String.format("Failed to get property: 0x%x, "
631                             + "areaId: 0x%x", propId, areaId));
632                 }
633             }
634             return handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
635         }
636     }
637 
638     /**
639      * Query CarPropertyValue with property id and areaId.
640      *
641      * <p> This method may take couple seconds to complete, so it needs to be called from an
642      * non-main thread.
643      *
644      * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
645      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
646      * request is failed.
647      * <ul>
648      *     <li>{@link CarInternalErrorException}
649      *     <li>{@link PropertyAccessDeniedSecurityException}
650      *     <li>{@link PropertyNotAvailableAndRetryException}
651      *     <li>{@link PropertyNotAvailableException}
652      *     <li>{@link IllegalArgumentException}
653      * </ul>
654      * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
655      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
656      * is failed.
657      * <ul>
658      *     <li>{@link IllegalStateException} when there is an error detected in cars.
659      *     <li>{@link IllegalArgumentException} when the property in the areaId is not supplied.
660      * </ul>
661      *
662      * @param propId Property Id
663      * @param areaId areaId
664      * @param <E> Value type of the property
665      *
666      * @throws {@link CarInternalErrorException} when there is an error detected in cars.
667      * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
668      * property.
669      * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
670      * not available and likely that retrying will be successful.
671      * @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
672      * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
673      *
674      * @return CarPropertyValue. Null if property's id is invalid.
675      */
676     @Nullable
getProperty(int propId, int areaId)677     public <E> CarPropertyValue<E> getProperty(int propId, int areaId) {
678         checkSupportedProperty(propId);
679 
680         try {
681             CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);
682             return propVal;
683         } catch (RemoteException e) {
684             return handleRemoteExceptionFromCarService(e, null);
685         } catch (ServiceSpecificException e) {
686             if (mAppTargetSdk < Build.VERSION_CODES.R) {
687                 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
688                     return null;
689                 } else {
690                     throw new IllegalStateException(String.format("Failed to get property: 0x%x, "
691                             + "areaId: 0x%x", propId, areaId));
692                 }
693             }
694             return handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
695         }
696     }
697 
698     /**
699      * Set value of car property by areaId.
700      *
701      * <p>If multiple clients set a property for the same area id simultaneously, which one takes
702      * precedence is undefined. Typically, the last set operation (in the order that they are issued
703      * to the car's ECU) overrides the previous set operations.
704      *
705      * <p> This method may take couple seconds to complete, so it needs to be called form an
706      * non-main thread.
707      *
708      * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
709      * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
710      * request is failed.
711      * <ul>
712      *     <li>{@link CarInternalErrorException}
713      *     <li>{@link PropertyAccessDeniedSecurityException}
714      *     <li>{@link PropertyNotAvailableAndRetryException}
715      *     <li>{@link PropertyNotAvailableException}
716      *     <li>{@link IllegalArgumentException}
717      * </ul>
718      * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
719      * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
720      * is failed.
721      * <ul>
722      *     <li>{@link RuntimeException} when the property is temporarily not available.
723      *     <li>{@link IllegalStateException} when there is an error detected in cars.
724      *     <li>{@link IllegalArgumentException} when the property in the areaId is not supplied
725      * </ul>
726      *
727      * @param clazz The class object for the CarPropertyValue
728      * @param propId Property ID
729      * @param areaId areaId
730      * @param val Value of CarPropertyValue
731      * @param <E> data type of the given property, for example property that was
732      * defined as {@code VEHICLE_VALUE_TYPE_INT32} in vehicle HAL could be accessed using
733      * {@code Integer.class}.
734      *
735      * @throws {@link CarInternalErrorException} when there is an error detected in cars.
736      * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
737      * property.
738      * @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
739      * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
740      * not available and likely that retrying will be successful.
741      * @throws {@link IllegalStateException} when get an unexpected error code.
742      * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
743      */
setProperty(@onNull Class<E> clazz, int propId, int areaId, @NonNull E val)744     public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) {
745         if (DBG) {
746             Log.d(TAG, "setProperty, propId: 0x" + toHexString(propId)
747                     + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val);
748         }
749         checkSupportedProperty(propId);
750         try {
751             if (mCarPropertyEventToService == null) {
752                 mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
753             }
754             mService.setProperty(new CarPropertyValue<>(propId, areaId, val),
755                     mCarPropertyEventToService);
756         } catch (RemoteException e) {
757             handleRemoteExceptionFromCarService(e);
758         } catch (ServiceSpecificException e) {
759             if (mAppTargetSdk < Build.VERSION_CODES.R) {
760                 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
761                     throw new RuntimeException(String.format("Failed to set property: 0x%x, "
762                             + "areaId: 0x%x", propId, areaId));
763                 } else {
764                     throw new IllegalStateException(String.format("Failed to set property: 0x%x, "
765                             + "areaId: 0x%x", propId, areaId));
766                 }
767             }
768             handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
769         }
770     }
771 
772     /**
773      * Modifies a property.  If the property modification doesn't occur, an error event shall be
774      * generated and propagated back to the application.
775      *
776      * <p> This method may take couple seconds to complete, so it needs to be called from an
777      * non-main thread.
778      *
779      * @param prop Property ID to modify
780      * @param areaId AreaId to apply the modification.
781      * @param val Value to set
782      */
setBooleanProperty(int prop, int areaId, boolean val)783     public void setBooleanProperty(int prop, int areaId, boolean val) {
784         setProperty(Boolean.class, prop, areaId, val);
785     }
786 
787     /**
788      * Set float value of property
789      *
790      * <p> This method may take couple seconds to complete, so it needs to be called from an
791      * non-main thread.
792      *
793      * @param prop Property ID to modify
794      * @param areaId AreaId to apply the modification
795      * @param val Value to set
796      */
setFloatProperty(int prop, int areaId, float val)797     public void setFloatProperty(int prop, int areaId, float val) {
798         setProperty(Float.class, prop, areaId, val);
799     }
800 
801     /**
802      * Set int value of property
803      *
804      * <p> This method may take couple seconds to complete, so it needs to be called from an
805      * non-main thread.
806      *
807      * @param prop Property ID to modify
808      * @param areaId AreaId to apply the modification
809      * @param val Value to set
810      */
setIntProperty(int prop, int areaId, int val)811     public void setIntProperty(int prop, int areaId, int val) {
812         setProperty(Integer.class, prop, areaId, val);
813     }
814 
815     // Handles ServiceSpecificException in CarService for R and later version.
handleCarServiceSpecificException(int errorCode, int propId, int areaId, T returnValue)816     private <T> T handleCarServiceSpecificException(int errorCode, int propId, int areaId,
817             T returnValue) {
818         switch (errorCode) {
819             case VehicleHalStatusCode.STATUS_NOT_AVAILABLE:
820                 throw new PropertyNotAvailableException(propId, areaId);
821             case VehicleHalStatusCode.STATUS_TRY_AGAIN:
822                 throw new PropertyNotAvailableAndRetryException(propId, areaId);
823             case VehicleHalStatusCode.STATUS_ACCESS_DENIED:
824                 throw new PropertyAccessDeniedSecurityException(propId, areaId);
825             case VehicleHalStatusCode.STATUS_INTERNAL_ERROR:
826                 throw new CarInternalErrorException(propId, areaId);
827             default:
828                 Log.e(TAG, "Invalid errorCode: " + errorCode + " in CarService");
829         }
830         return returnValue;
831     }
832 
833     /**
834      * Checks if the given property can be exposed to by this manager.
835      *
836      * <p>For example, properties related to user management should only be manipulated by
837      * {@code UserHalService}.
838      *
839      * @param propId property to be checked
840      *
841      * @throws IllegalArgumentException if the property is not supported.
842      */
checkSupportedProperty(int propId)843     private void checkSupportedProperty(int propId) {
844         switch (propId) {
845             case VehiclePropertyIds.INITIAL_USER_INFO:
846             case VehiclePropertyIds.SWITCH_USER:
847             case VehiclePropertyIds.CREATE_USER:
848             case VehiclePropertyIds.REMOVE_USER:
849             case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:
850                 throw new IllegalArgumentException("Unsupported property: "
851                         + VehiclePropertyIds.toString(propId) + " (" + propId + ")");
852         }
853     }
854 
855     private final class CarPropertyListeners
856             extends CarRatedFloatListeners<CarPropertyEventCallback> {
CarPropertyListeners(float rate)857         CarPropertyListeners(float rate) {
858             super(rate);
859         }
onPropertyChanged(final CarPropertyEvent event)860         void onPropertyChanged(final CarPropertyEvent event) {
861             // throw away old sensor data as oneway binder call can change order.
862             long updateTime = event.getCarPropertyValue().getTimestamp();
863             int areaId = event.getCarPropertyValue().getAreaId();
864             if (!needUpdateForAreaId(areaId, updateTime)) {
865                 if (DBG) {
866                     Log.w(TAG, "Dropping a stale event: " + event.toString());
867                 }
868                 return;
869             }
870             List<CarPropertyEventCallback> listeners;
871             synchronized (mActivePropertyListener) {
872                 listeners = new ArrayList<>(getListeners());
873             }
874             listeners.forEach(new Consumer<CarPropertyEventCallback>() {
875                 @Override
876                 public void accept(CarPropertyEventCallback listener) {
877                     if (needUpdateForSelectedListener(listener, updateTime)) {
878                         listener.onChangeEvent(event.getCarPropertyValue());
879                     }
880                 }
881             });
882         }
883 
onErrorEvent(final CarPropertyEvent event)884         void onErrorEvent(final CarPropertyEvent event) {
885             List<CarPropertyEventCallback> listeners;
886             CarPropertyValue value = event.getCarPropertyValue();
887             synchronized (mActivePropertyListener) {
888                 listeners = new ArrayList<>(getListeners());
889             }
890             listeners.forEach(new Consumer<CarPropertyEventCallback>() {
891                 @Override
892                 public void accept(CarPropertyEventCallback listener) {
893                     if (DBG) {
894                         Log.d(TAG, new StringBuilder().append("onErrorEvent for ")
895                                         .append("property: ").append(value.getPropertyId())
896                                         .append(" areaId: ").append(value.getAreaId())
897                                         .append(" errorCode: ").append(event.getErrorCode())
898                                         .toString());
899                     }
900 
901                     listener.onErrorEvent(value.getPropertyId(), value.getAreaId(),
902                             event.getErrorCode());
903 
904                 }
905             });
906         }
907     }
908 
909     /** @hide */
910     @Override
onCarDisconnected()911     public void onCarDisconnected() {
912         synchronized (mActivePropertyListener) {
913             mActivePropertyListener.clear();
914             mCarPropertyEventToService = null;
915         }
916     }
917 }
918