blob: 785e59810ceeb6e744d41f15efc366a45718c856 [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 com.android.car;
import static java.lang.Integer.toHexString;
import android.car.Car;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.ICarProperty;
import android.car.hardware.property.ICarPropertyEventListener;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import com.android.car.hal.PropertyHalService;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* This class implements the binder interface for ICarProperty.aidl to make it easier to create
* multiple managers that deal with Vehicle Properties. To create a new service, simply extend
* this class and call the super() constructor with the appropriate arguments for the new service.
* {@link CarHvacService} shows the basic usage.
*/
public class CarPropertyService extends ICarProperty.Stub
implements CarServiceBase, PropertyHalService.PropertyHalListener {
private static final boolean DBG = true;
private static final String TAG = "Property.service";
private final Context mContext;
private final Map<IBinder, Client> mClientMap = new ConcurrentHashMap<>();
private Map<Integer, CarPropertyConfig<?>> mConfigs;
private final PropertyHalService mHal;
private boolean mListenerIsSet = false;
private final Map<Integer, List<Client>> mPropIdClientMap = new ConcurrentHashMap<>();
private final Object mLock = new Object();
public CarPropertyService(Context context, PropertyHalService hal) {
if (DBG) {
Log.d(TAG, "CarPropertyService started!");
}
mHal = hal;
mContext = context;
}
// Helper class to keep track of listeners to this service
private class Client implements IBinder.DeathRecipient {
private final ICarPropertyEventListener mListener;
private final IBinder mListenerBinder;
private final SparseArray<Float> mRateMap = new SparseArray<Float>(); // key is propId
Client(ICarPropertyEventListener listener) {
mListener = listener;
mListenerBinder = listener.asBinder();
try {
mListenerBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(TAG, "Failed to link death for recipient. " + e);
throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG);
}
mClientMap.put(mListenerBinder, this);
}
void addProperty(int propId, float rate) {
mRateMap.put(propId, rate);
}
/**
* Client died. Remove the listener from HAL service and unregister if this is the last
* client.
*/
@Override
public void binderDied() {
if (DBG) {
Log.d(TAG, "binderDied " + mListenerBinder);
}
for (int i = 0; i < mRateMap.size(); i++) {
int propId = mRateMap.keyAt(i);
CarPropertyService.this.unregisterListenerBinderLocked(propId, mListenerBinder);
}
this.release();
}
ICarPropertyEventListener getListener() {
return mListener;
}
IBinder getListenerBinder() {
return mListenerBinder;
}
float getRate(int propId) {
// Return 0 if no key found, since that is the slowest rate.
return mRateMap.get(propId, (float) 0);
}
void release() {
mListenerBinder.unlinkToDeath(this, 0);
mClientMap.remove(mListenerBinder);
}
void removeProperty(int propId) {
mRateMap.remove(propId);
if (mRateMap.size() == 0) {
// Last property was released, remove the client.
this.release();
}
}
}
@Override
public void init() {
}
@Override
public void release() {
for (Client c : mClientMap.values()) {
c.release();
}
mClientMap.clear();
mPropIdClientMap.clear();
mHal.setListener(null);
mListenerIsSet = false;
}
@Override
public void dump(PrintWriter writer) {
}
@Override
public void registerListener(int propId, float rate, ICarPropertyEventListener listener) {
if (DBG) {
Log.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate);
}
if (mConfigs == null) {
mConfigs = mHal.getPropertyList();
}
if (mConfigs.get(propId) == null) {
// Do not attempt to register an invalid propId
Log.e(TAG, "registerListener: propId is not in config list: " + propId);
return;
}
ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
if (listener == null) {
Log.e(TAG, "registerListener: Listener is null.");
throw new IllegalArgumentException("listener cannot be null.");
}
IBinder listenerBinder = listener.asBinder();
synchronized (mLock) {
// Get the client for this listener
Client client = mClientMap.get(listenerBinder);
if (client == null) {
client = new Client(listener);
}
client.addProperty(propId, rate);
// Insert the client into the propId --> clients map
List<Client> clients = mPropIdClientMap.get(propId);
if (clients == null) {
clients = new CopyOnWriteArrayList<Client>();
mPropIdClientMap.put(propId, clients);
}
if (!clients.contains(client)) {
clients.add(client);
}
// Set the HAL listener if necessary
if (!mListenerIsSet) {
mHal.setListener(this);
}
// Set the new rate
if (rate > mHal.getSampleRate(propId)) {
mHal.subscribeProperty(propId, rate);
}
}
// Send the latest value(s) to the registering listener only
List<CarPropertyEvent> events = new LinkedList<CarPropertyEvent>();
if (mConfigs.get(propId).isGlobalProperty()) {
CarPropertyValue value = mHal.getProperty(propId, 0);
CarPropertyEvent event = new CarPropertyEvent(
CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
events.add(event);
} else {
for (int areaId : mConfigs.get(propId).getAreaIds()) {
CarPropertyValue value = mHal.getProperty(propId, areaId);
CarPropertyEvent event = new CarPropertyEvent(
CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
events.add(event);
}
}
try {
listener.onEvent(events);
} catch (RemoteException ex) {
// If we cannot send a record, its likely the connection snapped. Let the binder
// death handle the situation.
Log.e(TAG, "onEvent calling failed: " + ex);
}
}
@Override
public void unregisterListener(int propId, ICarPropertyEventListener listener) {
if (DBG) {
Log.d(TAG, "unregisterListener propId=0x" + toHexString(propId));
}
ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId));
if (listener == null) {
Log.e(TAG, "unregisterListener: Listener is null.");
throw new IllegalArgumentException("Listener is null");
}
IBinder listenerBinder = listener.asBinder();
synchronized (mLock) {
unregisterListenerBinderLocked(propId, listenerBinder);
}
}
private void unregisterListenerBinderLocked(int propId, IBinder listenerBinder) {
Client client = mClientMap.get(listenerBinder);
List<Client> propertyClients = mPropIdClientMap.get(propId);
if (mConfigs.get(propId) == null) {
// Do not attempt to register an invalid propId
Log.e(TAG, "unregisterListener: propId is not in config list:0x" + toHexString(propId));
return;
}
if ((client == null) || (propertyClients == null)) {
Log.e(TAG, "unregisterListenerBinderLocked: Listener was not previously registered.");
} else {
if (propertyClients.remove(client)) {
client.removeProperty(propId);
} else {
Log.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for "
+ "propId=0x" + toHexString(propId));
}
if (propertyClients.isEmpty()) {
// Last listener for this property unsubscribed. Clean up
mHal.unsubscribeProperty(propId);
mPropIdClientMap.remove(propId);
if (mPropIdClientMap.isEmpty()) {
// No more properties are subscribed. Turn off the listener.
mHal.setListener(null);
mListenerIsSet = false;
}
} else {
// Other listeners are still subscribed. Calculate the new rate
float maxRate = 0;
for (Client c : propertyClients) {
float rate = c.getRate(propId);
if (rate > maxRate) {
maxRate = rate;
}
}
// Set the new rate
mHal.subscribeProperty(propId, maxRate);
}
}
}
/**
* Return the list of properties that the caller may access.
* Should be called before get/setProperty().
*/
@Override
public List<CarPropertyConfig> getPropertyList() {
List<CarPropertyConfig> returnList = new ArrayList<CarPropertyConfig>();
if (mConfigs == null) {
// Cache the configs list to avoid subsequent binder calls
mConfigs = mHal.getPropertyList();
}
for (CarPropertyConfig c : mConfigs.values()) {
if (ICarImpl.hasPermission(mContext, mHal.getReadPermission(c.getPropertyId()))) {
// Only add properties the list if the process has permissions to read it
returnList.add(c);
}
}
if (DBG) {
Log.d(TAG, "getPropertyList returns " + returnList.size() + " configs");
}
return returnList;
}
@Override
public CarPropertyValue getProperty(int prop, int zone) {
if (mConfigs.get(prop) == null) {
// Do not attempt to register an invalid propId
Log.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop));
return null;
}
ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop));
return mHal.getProperty(prop, zone);
}
@Override
public void setProperty(CarPropertyValue prop) {
int propId = prop.getPropertyId();
if (mConfigs.get(propId) == null) {
// Do not attempt to register an invalid propId
Log.e(TAG, "setProperty: propId is not in config list:0x" + toHexString(propId));
return;
}
ICarImpl.assertPermission(mContext, mHal.getWritePermission(propId));
mHal.setProperty(prop);
}
// Implement PropertyHalListener interface
@Override
public void onPropertyChange(List<CarPropertyEvent> events) {
Map<IBinder, Pair<ICarPropertyEventListener, List<CarPropertyEvent>>> eventsToDispatch =
new HashMap<>();
for (CarPropertyEvent event : events) {
int propId = event.getCarPropertyValue().getPropertyId();
List<Client> clients = mPropIdClientMap.get(propId);
if (clients == null) {
Log.e(TAG, "onPropertyChange: no listener registered for propId=0x"
+ toHexString(propId));
continue;
}
for (Client c : clients) {
IBinder listenerBinder = c.getListenerBinder();
Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p =
eventsToDispatch.get(listenerBinder);
if (p == null) {
// Initialize the linked list for the listener
p = new Pair<>(c.getListener(), new LinkedList<CarPropertyEvent>());
eventsToDispatch.put(listenerBinder, p);
}
p.second.add(event);
}
}
// Parse the dispatch list to send events
for (Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p: eventsToDispatch.values()) {
try {
p.first.onEvent(p.second);
} catch (RemoteException ex) {
// If we cannot send a record, its likely the connection snapped. Let binder
// death handle the situation.
Log.e(TAG, "onEvent calling failed: " + ex);
}
}
}
@Override
public void onPropertySetError(int property, int area) {
List<Client> clients = mPropIdClientMap.get(property);
if (clients != null) {
List<CarPropertyEvent> eventList = new LinkedList<>();
eventList.add(createErrorEvent(property, area));
for (Client c : clients) {
try {
c.getListener().onEvent(eventList);
} catch (RemoteException ex) {
// If we cannot send a record, its likely the connection snapped. Let the binder
// death handle the situation.
Log.e(TAG, "onEvent calling failed: " + ex);
}
}
} else {
Log.e(TAG, "onPropertySetError called with no listener registered for propId=0x"
+ toHexString(property));
}
}
private static CarPropertyEvent createErrorEvent(int property, int area) {
return new CarPropertyEvent(CarPropertyEvent.PROPERTY_EVENT_ERROR,
new CarPropertyValue<>(property, area, null));
}
}