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