blob: 373c23a726e8b70e0dd8aee48e2f626080e04883 [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.Nullable;
import android.car.CarApiUtil;
import android.car.CarManagerBase;
import android.car.CarNotConnectedException;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
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.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* API for creating Car*Manager
* @hide
*/
public class CarPropertyManager implements CarManagerBase {
private final List<CarPropertyConfig> mConfigs;
private final boolean mDbg;
private final SingleMessageHandler<CarPropertyEvent> mHandler;
private final ICarProperty mService;
private final String mTag;
private static final int MSG_GENERIC_EVENT = 0;
private CarPropertyEventListenerToService mCarPropertyEventToService;
/** Record of locally active properties. Key is propertyId */
private final SparseArray<CarPropertyListeners> mActivePropertyListener =
new SparseArray<>();
/** Callback functions for property events */
public interface CarPropertyEventListener {
/** Called when a property is updated */
void onChangeEvent(CarPropertyValue value);
/** Called when an error is detected with a property */
void onErrorEvent(int propId, int zone);
}
/**
* Get an instance of the CarPropertyManager.
*/
public CarPropertyManager(IBinder service, @Nullable Handler handler, boolean dbg, String tag) {
mDbg = dbg;
mTag = tag;
mService = ICarProperty.Stub.asInterface(service);
try {
mConfigs = mService.getPropertyList();
} catch (Exception e) {
Log.e(mTag, "getPropertyList exception ", e);
throw new RuntimeException(e);
}
if (handler == null) {
mHandler = null;
return;
}
mHandler = new SingleMessageHandler<CarPropertyEvent>(handler.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();
}
}
}
};
}
/** Use to register or update Callback for properties */
public boolean registerListener(CarPropertyEventListener listener, int propertyId, float rate)
throws CarNotConnectedException {
synchronized (mActivePropertyListener) {
if (mCarPropertyEventToService == null) {
mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
}
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(listener, rate)) {
needsServerUpdate = true;
}
if (needsServerUpdate) {
if (!registerOrUpdatePropertyListener(propertyId, rate)) {
return false;
}
}
}
return true;
}
private boolean registerOrUpdatePropertyListener(int propertyId, float rate)
throws CarNotConnectedException {
try {
mService.registerListener(propertyId, rate, mCarPropertyEventToService);
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
return true;
}
private 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 sensor update for the given listener. If there are multiple registrations for
* this listener, all listening will be stopped.
* @param listener
*/
public void unregisterListener(CarPropertyEventListener listener) {
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(listener, prop);
}
}
}
/**
* Stop getting sensor update for the given listener and sensor. If the same listener is used
* for other sensors, those subscriptions will not be affected.
* @param listener
* @param propertyId
*/
public void unregisterListener(CarPropertyEventListener listener, int propertyId) {
synchronized (mActivePropertyListener) {
doUnregisterListenerLocked(listener, propertyId);
}
}
private void doUnregisterListenerLocked(CarPropertyEventListener 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) {
//ignore
}
mActivePropertyListener.remove(propertyId);
} else if (needsServerUpdate) {
try {
registerOrUpdatePropertyListener(propertyId, listeners.getRate());
} catch (CarNotConnectedException e) {
// ignore
}
}
}
}
/**
* @return List of properties implemented by this car that the application may access.
*/
public List<CarPropertyConfig> getPropertyList() {
return mConfigs;
}
/**
* @return List of properties implemented by this car in given property ID list that application
* may access.
*/
public List<CarPropertyConfig> getPropertyList(ArraySet<Integer> propertyIds) {
List<CarPropertyConfig> configs = new ArrayList<>();
for (CarPropertyConfig c : mConfigs) {
if (propertyIds.contains(c.getPropertyId())) {
configs.add(c);
}
}
return configs;
}
/**
* Check whether a given property is available or disabled based on the car's current state.
* @return true if STATUS_AVAILABLE, false otherwise (eg STATUS_UNAVAILABLE)
* @throws CarNotConnectedException
*/
public boolean isPropertyAvailable(int propId, int area) throws CarNotConnectedException {
try {
CarPropertyValue propValue = mService.getProperty(propId, area);
return (propValue != null)
&& (propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE);
} catch (RemoteException e) {
Log.e(mTag, "isPropertyAvailable failed with " + e.toString()
+ ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
throw new CarNotConnectedException(e);
}
}
/**
* Returns value of a bool property
*
* @param prop Property ID to get
* @param area Area of the property to get
*/
public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException {
CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area);
return carProp != null ? carProp.getValue() : false;
}
/**
* Returns value of a float property
*
* @param prop Property ID to get
* @param area Area of the property to get
*/
public float getFloatProperty(int prop, int area) throws CarNotConnectedException {
CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area);
return carProp != null ? carProp.getValue() : 0f;
}
/**
* Returns value of a integer property
*
* @param prop Property ID to get
* @param area Zone of the property to get
*/
public int getIntProperty(int prop, int area) throws CarNotConnectedException {
CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
return carProp != null ? carProp.getValue() : 0;
}
/**
* Returns value of a integer array property
*
* @param prop Property ID to get
* @param area Zone of the property to get
*/
public int[] getIntArrayProperty(int prop, int area) throws CarNotConnectedException {
CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, prop, area);
return carProp != null ? toIntArray(carProp.getValue()) : new int[0];
}
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;
}
/** Return CarPropertyValue */
@SuppressWarnings("unchecked")
public <E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int area)
throws CarNotConnectedException {
if (mDbg) {
Log.d(mTag, "getProperty, propId: 0x" + toHexString(propId)
+ ", area: 0x" + toHexString(area) + ", class: " + clazz);
}
try {
CarPropertyValue<E> propVal = mService.getProperty(propId, area);
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) {
Log.e(mTag, "getProperty failed with " + e.toString()
+ ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
throw new CarNotConnectedException(e);
}
}
/** Return raw CarPropertyValue */
public <E> CarPropertyValue<E> getProperty(int propId, int area)
throws CarNotConnectedException {
try {
CarPropertyValue<E> propVal = mService.getProperty(propId, area);
return propVal;
} catch (RemoteException e) {
Log.e(mTag, "getProperty failed with " + e.toString()
+ ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e);
throw new CarNotConnectedException(e);
}
}
/** Set CarPropertyValue */
public <E> void setProperty(Class<E> clazz, int propId, int area, E val)
throws CarNotConnectedException {
if (mDbg) {
Log.d(mTag, "setProperty, propId: 0x" + toHexString(propId)
+ ", area: 0x" + toHexString(area) + ", class: " + clazz + ", val: " + val);
}
try {
mService.setProperty(new CarPropertyValue<>(propId, area, val));
} catch (RemoteException e) {
Log.e(mTag, "setProperty failed with " + e.toString(), e);
throw new CarNotConnectedException(e);
}
}
/**
* Modifies a property. If the property modification doesn't occur, an error event shall be
* generated and propagated back to the application.
*
* @param prop Property ID to modify
* @param area Area to apply the modification.
* @param val Value to set
*/
public void setBooleanProperty(int prop, int area, boolean val)
throws CarNotConnectedException {
setProperty(Boolean.class, prop, area, val);
}
/** Set float value of property*/
public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException {
setProperty(Float.class, prop, area, val);
}
/** Set int value of property*/
public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException {
setProperty(Integer.class, prop, area, val);
}
private class CarPropertyListeners extends CarRatedFloatListeners<CarPropertyEventListener> {
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();
if (updateTime < mLastUpdateTime) {
Log.w(mTag, "dropping old property data");
return;
}
mLastUpdateTime = updateTime;
List<CarPropertyEventListener> listeners;
synchronized (mActivePropertyListener) {
listeners = new ArrayList<>(getListeners());
}
listeners.forEach(new Consumer<CarPropertyEventListener>() {
@Override
public void accept(CarPropertyEventListener listener) {
listener.onChangeEvent(event.getCarPropertyValue());
}
});
}
void onErrorEvent(final CarPropertyEvent event) {
List<CarPropertyEventListener> listeners;
CarPropertyValue value = event.getCarPropertyValue();
synchronized (mActivePropertyListener) {
listeners = new ArrayList<>(getListeners());
}
listeners.forEach(new Consumer<CarPropertyEventListener>() {
@Override
public void accept(CarPropertyEventListener listener) {
listener.onErrorEvent(value.getPropertyId(), value.getAreaId());
}
});
}
}
/** @hide */
@Override
public void onCarDisconnected() {
synchronized (mActivePropertyListener) {
mActivePropertyListener.clear();
mCarPropertyEventToService = null;
}
}
}