| /* |
| * Copyright (C) 2015 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; |
| |
| import android.Manifest; |
| import android.annotation.IntDef; |
| import android.annotation.RequiresPermission; |
| import android.car.Car; |
| import android.car.CarApiUtil; |
| import android.car.CarLibLog; |
| import android.car.CarManagerBase; |
| import android.car.CarNotConnectedException; |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.Handler.Callback; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.ref.WeakReference; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| /** |
| * API for monitoring car sensor data. |
| */ |
| public final class CarSensorManager implements CarManagerBase { |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED1 = 1; |
| /** |
| * This sensor represents vehicle speed in m/s. |
| * Sensor data in {@link CarSensorEvent} is a float which will be >= 0. |
| * This requires {@link Car#PERMISSION_SPEED} permission. |
| */ |
| public static final int SENSOR_TYPE_CAR_SPEED = 2; |
| /** |
| * Represents engine RPM of the car. Sensor data in {@link CarSensorEvent} is a float. |
| */ |
| public static final int SENSOR_TYPE_RPM = 3; |
| /** |
| * Total travel distance of the car in Kilometer. Sensor data is a float. |
| * This requires {@link Car#PERMISSION_MILEAGE} permission. |
| */ |
| public static final int SENSOR_TYPE_ODOMETER = 4; |
| /** |
| * Indicates fuel level of the car. |
| * In {@link CarSensorEvent}, floatValues[{@link CarSensorEvent#INDEX_FUEL_LEVEL_IN_PERCENTILE}] |
| * represents fuel level in percentile (0 to 100) while |
| * floatValues[{@link CarSensorEvent#INDEX_FUEL_LEVEL_IN_DISTANCE}] represents estimated range |
| * in Kilometer with the remaining fuel. |
| * Note that the gas mileage used for the estimation may not represent the current driving |
| * condition. |
| * This requires {@link Car#PERMISSION_FUEL} permission. |
| */ |
| public static final int SENSOR_TYPE_FUEL_LEVEL = 5; |
| /** |
| * Represents the current status of parking brake. Sensor data in {@link CarSensorEvent} is an |
| * intValues[0]. Value of 1 represents parking brake applied while 0 means the other way |
| * around. For this sensor, rate in {@link #registerListener(OnSensorChangedListener, int, int)} |
| * will be ignored and all changes will be notified. |
| */ |
| public static final int SENSOR_TYPE_PARKING_BRAKE = 6; |
| /** |
| * This represents the current position of transmission gear. Sensor data in |
| * {@link CarSensorEvent} is an intValues[0]. For the meaning of the value, check |
| * {@link CarSensorEvent#GEAR_NEUTRAL} and other GEAR_*. |
| */ |
| public static final int SENSOR_TYPE_GEAR = 7; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED8 = 8; |
| /** |
| * Day/night sensor. Sensor data is intValues[0]. |
| */ |
| public static final int SENSOR_TYPE_NIGHT = 9; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED10 = 10; |
| /** |
| * Represents the current driving status of car. Different user interaction should be used |
| * depending on the current driving status. Driving status is intValues[0]. |
| */ |
| public static final int SENSOR_TYPE_DRIVING_STATUS = 11; |
| /** |
| * Environment like temperature and pressure. |
| */ |
| public static final int SENSOR_TYPE_ENVIRONMENT = 12; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED13 = 13; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED14 = 14; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED15 = 15; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED16 = 16; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED17 = 17; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED18 = 18; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED19 = 19; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED20 = 20; |
| /** @hide */ |
| public static final int SENSOR_TYPE_RESERVED21 = 21; |
| |
| /** |
| * Sensor type bigger than this is invalid. Always update this after adding a new sensor. |
| * @hide |
| */ |
| private static final int SENSOR_TYPE_MAX = SENSOR_TYPE_ENVIRONMENT; |
| |
| /** |
| * Sensors defined in this range [{@link #SENSOR_TYPE_VENDOR_EXTENSION_START}, |
| * {@link #SENSOR_TYPE_VENDOR_EXTENSION_END}] is for each car vendor's to use. |
| * This should be only used for system app to access sensors not defined as standard types. |
| * So the sensor supported in this range can vary depending on car models / manufacturers. |
| * 3rd party apps should not use sensors in this range as they are not compatible across |
| * different cars. Additionally 3rd party apps trying to access sensor in this range will get |
| * security exception as their access is restricted to system apps. |
| * |
| * @hide |
| */ |
| public static final int SENSOR_TYPE_VENDOR_EXTENSION_START = 0x60000000; |
| public static final int SENSOR_TYPE_VENDOR_EXTENSION_END = 0x6fffffff; |
| |
| /** @hide */ |
| @IntDef({ |
| SENSOR_TYPE_CAR_SPEED, |
| SENSOR_TYPE_RPM, |
| SENSOR_TYPE_ODOMETER, |
| SENSOR_TYPE_FUEL_LEVEL, |
| SENSOR_TYPE_PARKING_BRAKE, |
| SENSOR_TYPE_GEAR, |
| SENSOR_TYPE_NIGHT, |
| SENSOR_TYPE_DRIVING_STATUS, |
| SENSOR_TYPE_ENVIRONMENT |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface SensorType {} |
| |
| /** Read sensor in default normal rate set for each sensors. This is default rate. */ |
| public static final int SENSOR_RATE_NORMAL = 3; |
| public static final int SENSOR_RATE_UI = 2; |
| public static final int SENSOR_RATE_FAST = 1; |
| /** Read sensor at the maximum rate. Actual rate will be different depending on the sensor. */ |
| public static final int SENSOR_RATE_FASTEST = 0; |
| |
| /** @hide */ |
| @IntDef({ |
| SENSOR_RATE_NORMAL, |
| SENSOR_RATE_UI, |
| SENSOR_RATE_FAST, |
| SENSOR_RATE_FASTEST |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface SensorRate {} |
| |
| private static final int MSG_SENSOR_EVENTS = 0; |
| |
| private final ICarSensor mService; |
| |
| private CarSensorEventListenerToService mCarSensorEventListenerToService; |
| |
| /** |
| * To keep record of locally active sensors. Key is sensor type. This is used as a basic lock |
| * for all client accesses. |
| */ |
| private final HashMap<Integer, CarSensorListeners> mActiveSensorListeners = |
| new HashMap<Integer, CarSensorListeners>(); |
| |
| /** Handles call back into projected apps. */ |
| private final Handler mHandler; |
| private final Callback mHandlerCallback = new Callback() { |
| @Override |
| public boolean handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_SENSOR_EVENTS: |
| synchronized(mActiveSensorListeners) { |
| List<CarSensorEvent> events = (List<CarSensorEvent>) msg.obj; |
| for (CarSensorEvent event: events) { |
| CarSensorListeners listeners = |
| mActiveSensorListeners.get(event.sensorType); |
| if (listeners != null) { |
| listeners.onSensorChanged(event); |
| } |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| }; |
| |
| |
| /** @hide */ |
| public CarSensorManager(IBinder service, Context context, Handler handler) { |
| mService = ICarSensor.Stub.asInterface(service); |
| mHandler = new Handler(handler.getLooper(), mHandlerCallback); |
| } |
| |
| /** @hide */ |
| @Override |
| public void onCarDisconnected() { |
| synchronized(mActiveSensorListeners) { |
| mActiveSensorListeners.clear(); |
| mCarSensorEventListenerToService = null; |
| } |
| } |
| |
| /** |
| * Give the list of CarSensors available in the connected car. |
| * @return array of all sensor types supported. |
| * @throws CarNotConnectedException if the connection to the car service has been lost. |
| */ |
| public int[] getSupportedSensors() throws CarNotConnectedException { |
| try { |
| return mService.getSupportedSensors(); |
| } catch (IllegalStateException e) { |
| CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); |
| } catch (RemoteException e) { |
| throw new CarNotConnectedException(e); |
| } |
| return new int[0]; |
| } |
| |
| /** |
| * Tells if given sensor is supported or not. |
| * @param sensorType |
| * @return true if the sensor is supported. |
| * @throws CarNotConnectedException if the connection to the car service has been lost. |
| */ |
| public boolean isSensorSupported(@SensorType int sensorType) throws CarNotConnectedException { |
| int[] sensors = getSupportedSensors(); |
| for (int sensorSupported: sensors) { |
| if (sensorType == sensorSupported) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check if given sensorList is including the sensorType. |
| * @param sensorList |
| * @param sensorType |
| * @return |
| */ |
| public static boolean isSensorSupported(int[] sensorList, @SensorType int sensorType) { |
| for (int sensorSupported: sensorList) { |
| if (sensorType == sensorSupported) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Listener for car sensor data change. |
| * Callbacks are called in the Looper context. |
| */ |
| public interface OnSensorChangedListener { |
| /** |
| * Called when there is a new sensor data from car. |
| * @param event Incoming sensor event for the given sensor type. |
| */ |
| void onSensorChanged(final CarSensorEvent event); |
| } |
| |
| /** |
| * Register {@link OnSensorChangedListener} to get repeated sensor updates. Multiple listeners |
| * can be registered for a single sensor or the same listener can be used for different sensors. |
| * If the same listener is registered again for the same sensor, it will be either ignored or |
| * updated depending on the rate. |
| * <p> |
| * Requires {@link Car#PERMISSION_SPEED} for {@link #SENSOR_TYPE_CAR_SPEED}, |
| * {@link Car#PERMISSION_MILEAGE} for {@link #SENSOR_TYPE_ODOMETER}, |
| * or {@link Car#PERMISSION_FUEL} for {@link #SENSOR_TYPE_FUEL_LEVEL}. |
| * |
| * @param listener |
| * @param sensorType sensor type to subscribe. |
| * @param rate how fast the sensor events are delivered. It should be one of |
| * {@link #SENSOR_RATE_FASTEST} or {@link #SENSOR_RATE_NORMAL}. Rate may not be respected |
| * especially when the same sensor is registered with different listener with different |
| * rates. |
| * @return if the sensor was successfully enabled. |
| * @throws CarNotConnectedException if the connection to the car service has been lost. |
| * @throws IllegalArgumentException for wrong argument like wrong rate |
| * @throws SecurityException if missing the appropriate permission |
| */ |
| @RequiresPermission(anyOf={Manifest.permission.ACCESS_FINE_LOCATION, Car.PERMISSION_SPEED, |
| Car.PERMISSION_MILEAGE, Car.PERMISSION_FUEL}, conditional=true) |
| public boolean registerListener(OnSensorChangedListener listener, @SensorType int sensorType, |
| @SensorRate int rate) throws CarNotConnectedException, IllegalArgumentException { |
| assertSensorType(sensorType); |
| if (rate != SENSOR_RATE_FASTEST && rate != SENSOR_RATE_NORMAL) { |
| throw new IllegalArgumentException("wrong rate " + rate); |
| } |
| synchronized(mActiveSensorListeners) { |
| if (mCarSensorEventListenerToService == null) { |
| mCarSensorEventListenerToService = new CarSensorEventListenerToService(this); |
| } |
| boolean needsServerUpdate = false; |
| CarSensorListeners listeners; |
| listeners = mActiveSensorListeners.get(sensorType); |
| if (listeners == null) { |
| listeners = new CarSensorListeners(rate); |
| mActiveSensorListeners.put(sensorType, listeners); |
| needsServerUpdate = true; |
| } |
| if (listeners.addAndUpdateRate(listener, rate)) { |
| needsServerUpdate = true; |
| } |
| if (needsServerUpdate) { |
| if (!registerOrUpdateSensorListener(sensorType, rate)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Stop getting sensor update for the given listener. If there are multiple registrations for |
| * this listener, all listening will be stopped. |
| * @param listener |
| * @throws CarNotConnectedException if the connection to the car service has been lost. |
| */ |
| public void unregisterListener(OnSensorChangedListener listener) { |
| //TODO: removing listener should reset update rate |
| synchronized(mActiveSensorListeners) { |
| Iterator<Integer> sensorIterator = mActiveSensorListeners.keySet().iterator(); |
| while (sensorIterator.hasNext()) { |
| Integer sensor = sensorIterator.next(); |
| doUnregisterListenerLocked(listener, sensor, sensorIterator); |
| } |
| } |
| } |
| |
| /** |
| * 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 sensorType |
| * @throws CarNotConnectedException if the connection to the car service has been lost. |
| */ |
| public void unregisterListener(OnSensorChangedListener listener, @SensorType int sensorType) { |
| synchronized(mActiveSensorListeners) { |
| doUnregisterListenerLocked(listener, sensorType, null); |
| } |
| } |
| |
| private void doUnregisterListenerLocked(OnSensorChangedListener listener, Integer sensor, |
| Iterator<Integer> sensorIterator) { |
| CarSensorListeners listeners = mActiveSensorListeners.get(sensor); |
| if (listeners != null) { |
| if (listeners.contains(listener)) { |
| listeners.remove(listener); |
| } |
| if (listeners.isEmpty()) { |
| try { |
| mService.unregisterSensorListener(sensor.intValue(), |
| mCarSensorEventListenerToService); |
| } catch (RemoteException e) { |
| //ignore |
| } |
| if (sensorIterator == null) { |
| mActiveSensorListeners.remove(sensor); |
| } else { |
| sensorIterator.remove(); |
| } |
| } |
| } |
| } |
| |
| private boolean registerOrUpdateSensorListener(int sensor, int rate) |
| throws CarNotConnectedException { |
| try { |
| if (!mService.registerOrUpdateSensorListener(sensor, rate, |
| mCarSensorEventListenerToService)) { |
| return false; |
| } |
| } catch (IllegalStateException e) { |
| CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); |
| } catch (RemoteException e) { |
| throw new CarNotConnectedException(e); |
| } |
| return true; |
| } |
| |
| /** |
| * Get the most recent CarSensorEvent for the given type. Note that latest sensor data from car |
| * will not be available if it was never subscribed before. This call will return immediately |
| * with null if there is no data available. |
| * @param type A sensor to request |
| * @return null if there was no sensor update since connected to the car. |
| * @throws CarNotConnectedException if the connection to the car service has been lost. |
| */ |
| public CarSensorEvent getLatestSensorEvent(@SensorType int type) |
| throws CarNotConnectedException { |
| assertSensorType(type); |
| try { |
| return mService.getLatestSensorEvent(type); |
| } catch (IllegalStateException e) { |
| CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); |
| } catch(RemoteException e) { |
| handleCarServiceRemoteExceptionAndThrow(e); |
| } |
| return null; |
| } |
| |
| private void handleCarServiceRemoteExceptionAndThrow(RemoteException e) |
| throws CarNotConnectedException { |
| if (Log.isLoggable(CarLibLog.TAG_SENSOR, Log.INFO)) { |
| Log.i(CarLibLog.TAG_SENSOR, "RemoteException from car service:" + e.getMessage()); |
| } |
| throw new CarNotConnectedException(); |
| } |
| |
| private void assertSensorType(int sensorType) { |
| if (sensorType == 0 || !((sensorType <= SENSOR_TYPE_MAX) || |
| ((sensorType >= SENSOR_TYPE_VENDOR_EXTENSION_START) && |
| (sensorType <= SENSOR_TYPE_VENDOR_EXTENSION_END)))) { |
| throw new IllegalArgumentException("invalid sensor type " + sensorType); |
| } |
| } |
| |
| private void handleOnSensorChanged(List<CarSensorEvent> events) { |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_SENSOR_EVENTS, events)); |
| } |
| |
| private static class CarSensorEventListenerToService extends ICarSensorEventListener.Stub { |
| private final WeakReference<CarSensorManager> mManager; |
| |
| public CarSensorEventListenerToService(CarSensorManager manager) { |
| mManager = new WeakReference<CarSensorManager>(manager); |
| } |
| |
| @Override |
| public void onSensorChanged(List<CarSensorEvent> events) { |
| CarSensorManager manager = mManager.get(); |
| if (manager != null) { |
| manager.handleOnSensorChanged(events); |
| } |
| } |
| } |
| |
| /** |
| * Represent listeners for a sensor. |
| */ |
| private class CarSensorListeners { |
| private final LinkedList<OnSensorChangedListener> mListeners = |
| new LinkedList<OnSensorChangedListener>(); |
| |
| private int mUpdateRate; |
| private long mLastUpdateTime = -1; |
| |
| CarSensorListeners(int rate) { |
| mUpdateRate = rate; |
| } |
| |
| boolean contains(OnSensorChangedListener listener) { |
| return mListeners.contains(listener); |
| } |
| |
| void remove(OnSensorChangedListener listener) { |
| mListeners.remove(listener); |
| } |
| |
| boolean isEmpty() { |
| return mListeners.isEmpty(); |
| } |
| |
| /** |
| * Add given listener to the list and update rate if necessary. |
| * @param listener if null, add part is skipped. |
| * @param updateRate |
| * @return true if rate was updated. Otherwise, returns false. |
| */ |
| boolean addAndUpdateRate(OnSensorChangedListener listener, int updateRate) { |
| if (!mListeners.contains(listener)) { |
| mListeners.add(listener); |
| } |
| if (mUpdateRate > updateRate) { |
| mUpdateRate = updateRate; |
| return true; |
| } |
| return false; |
| } |
| |
| void onSensorChanged(CarSensorEvent event) { |
| // throw away old sensor data as oneway binder call can change order. |
| long updateTime = event.timestamp; |
| if (updateTime < mLastUpdateTime) { |
| Log.w(CarLibLog.TAG_SENSOR, "dropping old sensor data"); |
| return; |
| } |
| mLastUpdateTime = updateTime; |
| for (OnSensorChangedListener listener: mListeners) { |
| listener.onSensorChanged(event); |
| } |
| } |
| } |
| } |