blob: bd472635eb48904d77aec661b6bb7458cd2d18fa [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.car.hardware.property;
import static java.lang.Integer.toHexString;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.car.Car;
import android.car.CarManagerBase;
import android.car.VehicleAreaType;
import android.car.VehiclePropertyIds;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import com.android.car.internal.CarRatedFloatListeners;
import com.android.car.internal.SingleMessageHandler;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* Provides an application interface for interacting with the Vehicle specific properties.
* For details about the individual properties, see the descriptions in
* hardware/interfaces/automotive/vehicle/types.hal
*/
public class CarPropertyManager extends CarManagerBase {
private static final boolean DBG = false;
private static final String TAG = "CarPropertyManager";
private static final int MSG_GENERIC_EVENT = 0;
private final SingleMessageHandler<CarPropertyEvent> mHandler;
private final ICarProperty mService;
private final int mAppTargetSdk;
private CarPropertyEventListenerToService mCarPropertyEventToService;
/** Record of locally active properties. Key is propertyId */
private final SparseArray<CarPropertyListeners> mActivePropertyListener =
new SparseArray<>();
/**
* Application registers {@link CarPropertyEventCallback} object to receive updates and changes
* to subscribed Vehicle specific properties.
*/
public interface CarPropertyEventCallback {
/**
* Called when a property is updated
* @param value Property that has been updated.
*/
void onChangeEvent(CarPropertyValue value);
/**
* Called when an error is detected when setting a property.
*
* @param propId Property ID which is detected an error.
* @param zone Zone which is detected an error.
*
* @see CarPropertyEventCallback#onErrorEvent(int, int, int)
*/
void onErrorEvent(int propId, int zone);
/**
* Called when an error is detected when setting a property.
*
* <p>Clients which changed the property value in the areaId most recently will receive
* this callback. If multiple clients set a property for the same area id simultaneously,
* which one takes precedence is undefined. Typically, the last set operation
* (in the order that they are issued to car's ECU) overrides the previous set operations.
* The delivered error reflects the error happened in the last set operation.
*
* @param propId Property ID which is detected an error.
* @param areaId AreaId which is detected an error.
* @param errorCode Error code is raised in the car.
*/
default void onErrorEvent(int propId, int areaId, @CarSetPropertyErrorCode int errorCode) {
if (DBG) {
Log.d(TAG, "onErrorEvent propertyId: 0x" + toHexString(propId) + " areaId:0x"
+ toHexString(areaId) + " ErrorCode: " + errorCode);
}
onErrorEvent(propId, areaId);
}
}
/** Read ON_CHANGE sensors */
public static final float SENSOR_RATE_ONCHANGE = 0f;
/** Read sensors at the rate of 1 hertz */
public static final float SENSOR_RATE_NORMAL = 1f;
/** Read sensors at the rate of 5 hertz */
public static final float SENSOR_RATE_UI = 5f;
/** Read sensors at the rate of 10 hertz */
public static final float SENSOR_RATE_FAST = 10f;
/** Read sensors at the rate of 100 hertz */
public static final float SENSOR_RATE_FASTEST = 100f;
/**
* Status to indicate that set operation failed. Try it again.
*/
public static final int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN = 1;
/**
* Status to indicate that set operation failed because of an invalid argument.
*/
public static final int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG = 2;
/**
* Status to indicate that set operation failed because the property is not available.
*/
public static final int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE = 3;
/**
* Status to indicate that set operation failed because car denied access to the property.
*/
public static final int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED = 4;
/**
* Status to indicate that set operation failed because of an general error in cars.
*/
public static final int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN = 5;
/** @hide */
@IntDef(prefix = {"CAR_SET_PROPERTY_ERROR_CODE_"}, value = {
CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN,
CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG,
CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE,
CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED,
CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CarSetPropertyErrorCode {}
/**
* Get an instance of the CarPropertyManager.
*
* Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
* @param car Car instance
* @param service ICarProperty instance
* @hide
*/
public CarPropertyManager(Car car, @NonNull ICarProperty service) {
super(car);
mService = service;
mAppTargetSdk = getContext().getApplicationInfo().targetSdkVersion;
Handler eventHandler = getEventHandler();
if (eventHandler == null) {
mHandler = null;
return;
}
mHandler = new SingleMessageHandler<CarPropertyEvent>(eventHandler.getLooper(),
MSG_GENERIC_EVENT) {
@Override
protected void handleEvent(CarPropertyEvent event) {
CarPropertyListeners listeners;
synchronized (mActivePropertyListener) {
listeners = mActivePropertyListener.get(
event.getCarPropertyValue().getPropertyId());
}
if (listeners != null) {
switch (event.getEventType()) {
case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE:
listeners.onPropertyChanged(event);
break;
case CarPropertyEvent.PROPERTY_EVENT_ERROR:
listeners.onErrorEvent(event);
break;
default:
throw new IllegalArgumentException();
}
}
}
};
}
/**
* Register {@link CarPropertyEventCallback} to get property updates. Multiple listeners
* can be registered for a single property or the same listener can be used for different
* properties. If the same listener is registered again for the same property, it will be
* updated to new rate.
* <p>Rate could be one of the following:
* <ul>
* <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li>
* <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li>
* <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li>
* <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li>
* <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li>
* </ul>
* <p>
* <b>Note:</b>Rate has no effect if the property has one of the following change modes:
* <ul>
* <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li>
* <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
* </ul>
* See {@link CarPropertyConfig#getChangeMode()} for details.
* If rate is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it will be registered
* with max sample rate.
* If rate is lower than {@link CarPropertyConfig#getMinSampleRate()}, it will be registered
* with min sample rate.
*
* @param callback CarPropertyEventCallback to be registered.
* @param propertyId PropertyId to subscribe
* @param rate how fast the property events are delivered in Hz.
* @return true if the listener is successfully registered.
* @throws SecurityException if missing the appropriate permission.
*/
public boolean registerCallback(@NonNull CarPropertyEventCallback callback,
int propertyId, @FloatRange(from = 0.0, to = 100.0) float rate) {
synchronized (mActivePropertyListener) {
if (mCarPropertyEventToService == null) {
mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
}
CarPropertyConfig config = getCarPropertyConfig(propertyId);
if (config == null) {
Log.e(TAG, "registerListener: propId is not in config list: " + propertyId);
return false;
}
if (config.getChangeMode() == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
rate = SENSOR_RATE_ONCHANGE;
}
boolean needsServerUpdate = false;
CarPropertyListeners listeners;
listeners = mActivePropertyListener.get(propertyId);
if (listeners == null) {
listeners = new CarPropertyListeners(rate);
mActivePropertyListener.put(propertyId, listeners);
needsServerUpdate = true;
}
if (listeners.addAndUpdateRate(callback, rate)) {
needsServerUpdate = true;
}
if (needsServerUpdate) {
if (!registerOrUpdatePropertyListener(propertyId, rate)) {
return false;
}
}
}
return true;
}
private boolean registerOrUpdatePropertyListener(int propertyId, float rate) {
try {
mService.registerListener(propertyId, rate, mCarPropertyEventToService);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, false);
}
return true;
}
private static class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub{
private final WeakReference<CarPropertyManager> mMgr;
CarPropertyEventListenerToService(CarPropertyManager mgr) {
mMgr = new WeakReference<>(mgr);
}
@Override
public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
CarPropertyManager manager = mMgr.get();
if (manager != null) {
manager.handleEvent(events);
}
}
}
private void handleEvent(List<CarPropertyEvent> events) {
if (mHandler != null) {
mHandler.sendEvents(events);
}
}
/**
* Stop getting property update for the given callback. If there are multiple registrations for
* this callback, all listening will be stopped.
* @param callback CarPropertyEventCallback to be unregistered.
*/
public void unregisterCallback(@NonNull CarPropertyEventCallback callback) {
synchronized (mActivePropertyListener) {
int [] propertyIds = new int[mActivePropertyListener.size()];
for (int i = 0; i < mActivePropertyListener.size(); i++) {
propertyIds[i] = mActivePropertyListener.keyAt(i);
}
for (int prop : propertyIds) {
doUnregisterListenerLocked(callback, prop);
}
}
}
/**
* Stop getting property update for the given callback and property. If the same callback is
* used for other properties, those subscriptions will not be affected.
*
* @param callback CarPropertyEventCallback to be unregistered.
* @param propertyId PropertyId to be unregistered.
*/
public void unregisterCallback(@NonNull CarPropertyEventCallback callback, int propertyId) {
synchronized (mActivePropertyListener) {
doUnregisterListenerLocked(callback, propertyId);
}
}
private void doUnregisterListenerLocked(CarPropertyEventCallback listener, int propertyId) {
CarPropertyListeners listeners = mActivePropertyListener.get(propertyId);
if (listeners != null) {
boolean needsServerUpdate = false;
if (listeners.contains(listener)) {
needsServerUpdate = listeners.remove(listener);
}
if (listeners.isEmpty()) {
try {
mService.unregisterListener(propertyId, mCarPropertyEventToService);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
// continue for local clean-up
}
mActivePropertyListener.remove(propertyId);
} else if (needsServerUpdate) {
registerOrUpdatePropertyListener(propertyId, listeners.getRate());
}
}
}
/**
* @return List of properties implemented by this car that the application may access.
*/
@NonNull
public List<CarPropertyConfig> getPropertyList() {
List<CarPropertyConfig> configs;
try {
configs = mService.getPropertyList();
} catch (RemoteException e) {
Log.e(TAG, "getPropertyList exception ", e);
return handleRemoteExceptionFromCarService(e, new ArrayList<CarPropertyConfig>());
}
return configs;
}
/**
* @param propertyIds property ID list
* @return List of properties implemented by this car in given property ID list that application
* may access.
*/
@NonNull
public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) {
int[] propIds = new int[propertyIds.size()];
int idx = 0;
for (int propId : propertyIds) {
checkSupportedProperty(propId);
propIds[idx++] = propId;
}
List<CarPropertyConfig> configs;
try {
configs = mService.getPropertyConfigList(propIds);
} catch (RemoteException e) {
Log.e(TAG, "getPropertyList exception ", e);
return handleRemoteExceptionFromCarService(e, new ArrayList<CarPropertyConfig>());
}
return configs;
}
/**
* Get CarPropertyConfig by property Id.
*
* @param propId Property ID
* @return {@link CarPropertyConfig} for the selected property.
* Null if the property is not available.
*/
@Nullable
public CarPropertyConfig<?> getCarPropertyConfig(int propId) {
checkSupportedProperty(propId);
List<CarPropertyConfig> configs;
try {
configs = mService.getPropertyConfigList(new int[] {propId});
} catch (RemoteException e) {
Log.e(TAG, "getPropertyList exception ", e);
return handleRemoteExceptionFromCarService(e, null);
}
return configs.size() == 0 ? null : configs.get(0);
}
/**
* Returns areaId contains the seletcted area for the property.
*
* @param propId Property ID
* @param area Area enum such as Enums in {@link android.car.VehicleAreaSeat}.
* @throws IllegalArgumentException if the property is not available in the vehicle for
* the selected area.
* @return AreaId contains the selected area for the property.
*/
public int getAreaId(int propId, int area) {
checkSupportedProperty(propId);
CarPropertyConfig<?> propConfig = getCarPropertyConfig(propId);
if (propConfig == null) {
throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId)
+ " is not available");
}
// For the global property, areaId is 0
if (propConfig.isGlobalProperty()) {
return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
}
for (int areaId : propConfig.getAreaIds()) {
if ((area & areaId) == area) {
return areaId;
}
}
throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId)
+ " is not available at the area: 0x" + toHexString(area));
}
/**
* Return read permission string for given property ID.
*
* @param propId Property ID to query
* @return String Permission needed to read this property. NULL if propId not available.
* @hide
*/
@Nullable
public String getReadPermission(int propId) {
if (DBG) {
Log.d(TAG, "getReadPermission, propId: 0x" + toHexString(propId));
}
checkSupportedProperty(propId);
try {
return mService.getReadPermission(propId);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, "");
}
}
/**
* Return write permission string for given property ID.
*
* @param propId Property ID to query
* @return String Permission needed to write this property. NULL if propId not available.
* @hide
*/
@Nullable
public String getWritePermission(int propId) {
if (DBG) {
Log.d(TAG, "getWritePermission, propId: 0x" + toHexString(propId));
}
checkSupportedProperty(propId);
try {
return mService.getWritePermission(propId);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, "");
}
}
/**
* Check whether a given property is available or disabled based on the car's current state.
* @param propId Property Id
* @param area AreaId of property
* @return true if STATUS_AVAILABLE, false otherwise (eg STATUS_UNAVAILABLE)
*/
public boolean isPropertyAvailable(int propId, int area) {
checkSupportedProperty(propId);
try {
CarPropertyValue propValue = mService.getProperty(propId, area);
return (propValue != null)
&& (propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, false);
}
}
/**
* Returns value of a bool property
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
* or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
* request is failed.
* <ul>
* <li>{@link CarInternalErrorException}
* <li>{@link PropertyAccessDeniedSecurityException}
* <li>{@link PropertyNotAvailableAndRetryException}
* <li>{@link PropertyNotAvailableException}
* <li>{@link IllegalArgumentException}
* </ul>
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
* earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions if the call
* fails.
* <ul>
* <li>{@link IllegalStateException} when there is an error detected in cars.
* <li>{@link IllegalArgumentException} when the property in the areaId is not supplied.
* </ul>
*
* @param prop Property ID to get
* @param area Area of the property to get
*
* @throws {@link CarInternalErrorException} when there is an error detected in cars.
* @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
* property.
* @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
* not available and likely that retrying will be successful.
* @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
* @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
*
* @return value of a bool property, {@code false} if can not get value from cars.
*/
public boolean getBooleanProperty(int prop, int area) {
checkSupportedProperty(prop);
CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area);
return handleNullAndPropertyStatus(carProp, area, false);
}
/**
* Returns value of a float property
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* <p> This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
*
* @param prop Property ID to get
* @param area Area of the property to get
*
* @throws {@link CarInternalErrorException} when there is an error detected in cars.
* @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
* property.
* @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
* not available and likely that retrying will be successful.
* @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
* @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
*
* @return value of a float property, 0 if can not get value from the cars.
*/
public float getFloatProperty(int prop, int area) {
checkSupportedProperty(prop);
CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area);
return handleNullAndPropertyStatus(carProp, area, 0f);
}
/**
* Returns value of an integer property
*
* <p> This method may take couple seconds to complete, so it needs to be called form an
* non-main thread.
*
* <p> This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
*
* @param prop Property ID to get
* @param area Zone of the property to get
*
* @throws {@link CarInternalErrorException} when there is an error detected in cars.
* @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
* property.
* @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
* not available and likely that retrying will be successful.
* @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
* @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
*
* @return value of an integer property, 0 if can not get the value from cars.
*/
public int getIntProperty(int prop, int area) {
checkSupportedProperty(prop);
CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
return handleNullAndPropertyStatus(carProp, area, 0);
}
/**
* Returns value of an integer array property
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* <p> This method has the same exception behavior as {@link #getBooleanProperty(int, int)}.
*
* @param prop Property ID to get
* @param area Zone of the property to get
*
* @throws {@link CarInternalErrorException} when there is an error detected in cars.
* @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
* property.
* @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
* not available and likely that retrying will be successful.
* @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
* @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
*
* @return value of an integer array property, an empty integer array if can not get the value
* from cars.
*/
@NonNull
public int[] getIntArrayProperty(int prop, int area) {
checkSupportedProperty(prop);
CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, prop, area);
Integer[] res = handleNullAndPropertyStatus(carProp, area, new Integer[0]);
return toIntArray(res);
}
private static int[] toIntArray(Integer[] input) {
int len = input.length;
int[] arr = new int[len];
for (int i = 0; i < len; i++) {
arr[i] = input[i];
}
return arr;
}
private <T> T handleNullAndPropertyStatus(CarPropertyValue<T> propertyValue, int areaId,
T defaultValue) {
if (propertyValue == null) {
return defaultValue;
}
// Keeps the same behavior as android R.
if (mAppTargetSdk < Build.VERSION_CODES.S) {
return propertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE
? propertyValue.getValue() : defaultValue;
}
// throws new exceptions in android S.
switch (propertyValue.getStatus()) {
case CarPropertyValue.STATUS_ERROR:
throw new CarInternalErrorException(propertyValue.getPropertyId(), areaId);
case CarPropertyValue.STATUS_UNAVAILABLE:
throw new PropertyNotAvailableException(propertyValue.getPropertyId(), areaId);
default:
return propertyValue.getValue();
}
}
/**
* Return CarPropertyValue
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
* or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
* request is failed.
* <ul>
* <li>{@link CarInternalErrorException}
* <li>{@link PropertyAccessDeniedSecurityException}
* <li>{@link PropertyNotAvailableAndRetryException}
* <li>{@link PropertyNotAvailableException}
* <li>{@link IllegalArgumentException}
* </ul>
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
* earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
* is failed.
* <ul>
* <li>{@link IllegalStateException} when there is an error detected in cars.
* <li>{@link IllegalArgumentException} when the property in the areaId is not supplied.
* </ul>
*
* @param clazz The class object for the CarPropertyValue
* @param propId Property ID to get
* @param areaId Zone of the property to get
*
* @throws {@link CarInternalErrorException} when there is an error detected in cars.
* @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
* property.
* @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
* not available and likely that retrying will be successful.
* @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
* @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
*
* @return CarPropertyValue. Null if property's id is invalid.
*/
@SuppressWarnings("unchecked")
@Nullable
public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) {
if (DBG) {
Log.d(TAG, "getProperty, propId: 0x" + toHexString(propId)
+ ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz);
}
checkSupportedProperty(propId);
try {
CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);
if (propVal != null && propVal.getValue() != null) {
Class<?> actualClass = propVal.getValue().getClass();
if (actualClass != clazz) {
throw new IllegalArgumentException("Invalid property type. " + "Expected: "
+ clazz + ", but was: " + actualClass);
}
}
return propVal;
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, null);
} catch (ServiceSpecificException e) {
// For pre R apps, throws the old exceptions.
if (mAppTargetSdk < Build.VERSION_CODES.R) {
if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
return null;
} else {
throw new IllegalStateException(String.format("Failed to get property: 0x%x, "
+ "areaId: 0x%x", propId, areaId));
}
}
return handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
}
}
/**
* Query CarPropertyValue with property id and areaId.
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
* or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
* request is failed.
* <ul>
* <li>{@link CarInternalErrorException}
* <li>{@link PropertyAccessDeniedSecurityException}
* <li>{@link PropertyNotAvailableAndRetryException}
* <li>{@link PropertyNotAvailableException}
* <li>{@link IllegalArgumentException}
* </ul>
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
* earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
* is failed.
* <ul>
* <li>{@link IllegalStateException} when there is an error detected in cars.
* <li>{@link IllegalArgumentException} when the property in the areaId is not supplied.
* </ul>
*
* @param propId Property Id
* @param areaId areaId
* @param <E> Value type of the property
*
* @throws {@link CarInternalErrorException} when there is an error detected in cars.
* @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
* property.
* @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
* not available and likely that retrying will be successful.
* @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
* @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
*
* @return CarPropertyValue. Null if property's id is invalid.
*/
@Nullable
public <E> CarPropertyValue<E> getProperty(int propId, int areaId) {
checkSupportedProperty(propId);
try {
CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);
return propVal;
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, null);
} catch (ServiceSpecificException e) {
if (mAppTargetSdk < Build.VERSION_CODES.R) {
if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
return null;
} else {
throw new IllegalStateException(String.format("Failed to get property: 0x%x, "
+ "areaId: 0x%x", propId, areaId));
}
}
return handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
}
}
/**
* Set value of car property by areaId.
*
* <p>If multiple clients set a property for the same area id simultaneously, which one takes
* precedence is undefined. Typically, the last set operation (in the order that they are issued
* to the car's ECU) overrides the previous set operations.
*
* <p> This method may take couple seconds to complete, so it needs to be called form an
* non-main thread.
*
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal
* or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when
* request is failed.
* <ul>
* <li>{@link CarInternalErrorException}
* <li>{@link PropertyAccessDeniedSecurityException}
* <li>{@link PropertyNotAvailableAndRetryException}
* <li>{@link PropertyNotAvailableException}
* <li>{@link IllegalArgumentException}
* </ul>
* <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion}
* earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request
* is failed.
* <ul>
* <li>{@link RuntimeException} when the property is temporarily not available.
* <li>{@link IllegalStateException} when there is an error detected in cars.
* <li>{@link IllegalArgumentException} when the property in the areaId is not supplied
* </ul>
*
* @param clazz The class object for the CarPropertyValue
* @param propId Property ID
* @param areaId areaId
* @param val Value of CarPropertyValue
* @param <E> data type of the given property, for example property that was
* defined as {@code VEHICLE_VALUE_TYPE_INT32} in vehicle HAL could be accessed using
* {@code Integer.class}.
*
* @throws {@link CarInternalErrorException} when there is an error detected in cars.
* @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the
* property.
* @throws {@link PropertyNotAvailableException} when the property is temporarily not available.
* @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily
* not available and likely that retrying will be successful.
* @throws {@link IllegalStateException} when get an unexpected error code.
* @throws {@link IllegalArgumentException} when the property in the areaId is not supplied.
*/
public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) {
if (DBG) {
Log.d(TAG, "setProperty, propId: 0x" + toHexString(propId)
+ ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val);
}
checkSupportedProperty(propId);
try {
if (mCarPropertyEventToService == null) {
mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
}
mService.setProperty(new CarPropertyValue<>(propId, areaId, val),
mCarPropertyEventToService);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
} catch (ServiceSpecificException e) {
if (mAppTargetSdk < Build.VERSION_CODES.R) {
if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
throw new RuntimeException(String.format("Failed to set property: 0x%x, "
+ "areaId: 0x%x", propId, areaId));
} else {
throw new IllegalStateException(String.format("Failed to set property: 0x%x, "
+ "areaId: 0x%x", propId, areaId));
}
}
handleCarServiceSpecificException(e.errorCode, propId, areaId, null);
}
}
/**
* Modifies a property. If the property modification doesn't occur, an error event shall be
* generated and propagated back to the application.
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* @param prop Property ID to modify
* @param areaId AreaId to apply the modification.
* @param val Value to set
*/
public void setBooleanProperty(int prop, int areaId, boolean val) {
setProperty(Boolean.class, prop, areaId, val);
}
/**
* Set float value of property
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* @param prop Property ID to modify
* @param areaId AreaId to apply the modification
* @param val Value to set
*/
public void setFloatProperty(int prop, int areaId, float val) {
setProperty(Float.class, prop, areaId, val);
}
/**
* Set int value of property
*
* <p> This method may take couple seconds to complete, so it needs to be called from an
* non-main thread.
*
* @param prop Property ID to modify
* @param areaId AreaId to apply the modification
* @param val Value to set
*/
public void setIntProperty(int prop, int areaId, int val) {
setProperty(Integer.class, prop, areaId, val);
}
// Handles ServiceSpecificException in CarService for R and later version.
private <T> T handleCarServiceSpecificException(int errorCode, int propId, int areaId,
T returnValue) {
switch (errorCode) {
case VehicleHalStatusCode.STATUS_NOT_AVAILABLE:
throw new PropertyNotAvailableException(propId, areaId);
case VehicleHalStatusCode.STATUS_TRY_AGAIN:
throw new PropertyNotAvailableAndRetryException(propId, areaId);
case VehicleHalStatusCode.STATUS_ACCESS_DENIED:
throw new PropertyAccessDeniedSecurityException(propId, areaId);
case VehicleHalStatusCode.STATUS_INTERNAL_ERROR:
throw new CarInternalErrorException(propId, areaId);
default:
Log.e(TAG, "Invalid errorCode: " + errorCode + " in CarService");
}
return returnValue;
}
/**
* Checks if the given property can be exposed to by this manager.
*
* <p>For example, properties related to user management should only be manipulated by
* {@code UserHalService}.
*
* @param propId property to be checked
*
* @throws IllegalArgumentException if the property is not supported.
*/
private void checkSupportedProperty(int propId) {
switch (propId) {
case VehiclePropertyIds.INITIAL_USER_INFO:
case VehiclePropertyIds.SWITCH_USER:
case VehiclePropertyIds.CREATE_USER:
case VehiclePropertyIds.REMOVE_USER:
case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:
throw new IllegalArgumentException("Unsupported property: "
+ VehiclePropertyIds.toString(propId) + " (" + propId + ")");
}
}
private final class CarPropertyListeners
extends CarRatedFloatListeners<CarPropertyEventCallback> {
CarPropertyListeners(float rate) {
super(rate);
}
void onPropertyChanged(final CarPropertyEvent event) {
// throw away old sensor data as oneway binder call can change order.
long updateTime = event.getCarPropertyValue().getTimestamp();
int areaId = event.getCarPropertyValue().getAreaId();
if (!needUpdateForAreaId(areaId, updateTime)) {
if (DBG) {
Log.w(TAG, "Dropping a stale event: " + event.toString());
}
return;
}
List<CarPropertyEventCallback> listeners;
synchronized (mActivePropertyListener) {
listeners = new ArrayList<>(getListeners());
}
listeners.forEach(listener -> {
if (contains(listener)
&& needUpdateForSelectedListener(listener, updateTime)) {
listener.onChangeEvent(event.getCarPropertyValue());
}
});
}
void onErrorEvent(final CarPropertyEvent event) {
List<CarPropertyEventCallback> listeners;
CarPropertyValue value = event.getCarPropertyValue();
synchronized (mActivePropertyListener) {
listeners = new ArrayList<>(getListeners());
}
listeners.forEach(listener -> {
if (contains(listener)) {
if (DBG) {
Log.d(TAG, new StringBuilder().append("onErrorEvent for ")
.append("property: ").append(value.getPropertyId())
.append(" areaId: ").append(value.getAreaId())
.append(" errorCode: ").append(event.getErrorCode())
.toString());
}
listener.onErrorEvent(value.getPropertyId(), value.getAreaId(),
event.getErrorCode());
}
});
}
}
/** @hide */
@Override
public void onCarDisconnected() {
synchronized (mActivePropertyListener) {
mActivePropertyListener.clear();
mCarPropertyEventToService = null;
}
}
}