blob: 82d75de6d18b1406f5f58176d57e84aeab7c17ca [file] [log] [blame]
/*
* 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.hvac;
import static java.lang.Integer.toHexString;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarManagerBase;
import android.car.CarNotConnectedException;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.List;
/**
* API for controlling HVAC system in cars
* @hide
*/
@SystemApi
public class CarHvacManager implements CarManagerBase {
public final static boolean DBG = true;
public final static String TAG = "CarHvacManager";
/**
* HVAC property IDs for get/set methods
*/
@IntDef({
HvacPropertyId.MIRROR_DEFROSTER_ON,
HvacPropertyId.AUTOMATIC_MODE_ON,
HvacPropertyId.AIR_RECIRCULATION_ON,
HvacPropertyId.STEERING_WHEEL_TEMP,
HvacPropertyId.MAX_GLOBAL_PROPERTY_ID,
HvacPropertyId.ZONED_TEMP_SETPOINT,
HvacPropertyId.ZONED_TEMP_ACTUAL,
HvacPropertyId.ZONED_TEMP_IS_FAHRENHEIT,
HvacPropertyId.ZONED_FAN_SPEED_SETPOINT,
HvacPropertyId.ZONED_FAN_SPEED_RPM,
HvacPropertyId.ZONED_FAN_POSITION_AVAILABLE,
HvacPropertyId.ZONED_FAN_POSITION,
HvacPropertyId.ZONED_SEAT_TEMP,
HvacPropertyId.ZONED_AC_ON,
HvacPropertyId.WINDOW_DEFROSTER_ON,
})
public @interface HvacPropertyId {
/**
* Global HVAC properties. There is only a single instance in a car.
* Global properties are in the range of 0-0x3FFF.
*/
/** Mirror defrosters state, bool. */
int MIRROR_DEFROSTER_ON = 0x0001;
/** HVAC is in automatic mode, bool. */
int AUTOMATIC_MODE_ON = 0x0003;
/** Air recirculation is active, bool. */
int AIR_RECIRCULATION_ON = 0x0004;
/** Steering wheel temp: negative values indicate cooling, positive values indicate
* heat, int. */
int STEERING_WHEEL_TEMP = 0x0005;
/** The maximum id that can be assigned to global (non-zoned) property. */
int MAX_GLOBAL_PROPERTY_ID = 0x3fff;
/**
* ZONED_* represents properties available on a per-zone basis. All zones in a car are
* not required to have the same properties. Zone specific properties start at 0x4000.
*/
/** Temperature setpoint desired by the user, in terms of F or C, depending on
* TEMP_IS_FAHRENHEIT, int */
int ZONED_TEMP_SETPOINT = 0x4001;
/** Actual zone temperature is read only integer, in terms of F or C, int. */
int ZONED_TEMP_ACTUAL = 0x4002;
/** Temperature is in degrees fahrenheit if this is true, bool. */
int ZONED_TEMP_IS_FAHRENHEIT = 0x4003;
/** Fan speed setpoint is an integer from 0-n, depending on the number of fan speeds
* available. Selection determines the fan position, int. */
int ZONED_FAN_SPEED_SETPOINT = 0x4004;
/** Actual fan speed is a read-only value, expressed in RPM, int. */
int ZONED_FAN_SPEED_RPM = 0x4005;
/** Fan position available is a bitmask of positions available for each zone, int. */
int ZONED_FAN_POSITION_AVAILABLE = 0x4006;
/** Current fan position setting, int. */
int ZONED_FAN_POSITION = 0x4007;
/** Seat temperature is negative for cooling, positive for heating. Temperature is a
* setting, i.e. -3 to 3 for 3 levels of cooling and 3 levels of heating. int. */
int ZONED_SEAT_TEMP = 0x4008;
/** Air conditioner state, bool */
int ZONED_AC_ON = 0x4009;
/** Defroster is based off of window position, bool */
int WINDOW_DEFROSTER_ON = 0x5001;
}
// Constants handled in the handler (see mHandler below).
private final static int MSG_HVAC_EVENT = 0;
/** Callback functions for HVAC events */
public interface CarHvacEventListener {
/** Called when an HVAC property is updated */
void onChangeEvent(final CarPropertyValue value);
/** Called when an error is detected with a property */
void onErrorEvent(final int propertyId, final int zone);
}
private final ICarHvac mService;
private final ArraySet<CarHvacEventListener> mListeners = new ArraySet<>();
private CarHvacEventListenerToService mListenerToService = null;
private static final class EventCallbackHandler extends Handler {
WeakReference<CarHvacManager> mMgr;
EventCallbackHandler(CarHvacManager mgr, Looper looper) {
super(looper);
mMgr = new WeakReference<>(mgr);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_HVAC_EVENT:
CarHvacManager mgr = mMgr.get();
if (mgr != null) {
mgr.dispatchEventToClient((CarHvacEvent) msg.obj);
}
break;
default:
Log.e(TAG, "Event type not handled?" + msg);
break;
}
}
}
private final Handler mHandler;
private static class CarHvacEventListenerToService extends ICarHvacEventListener.Stub {
private final WeakReference<CarHvacManager> mManager;
public CarHvacEventListenerToService(CarHvacManager manager) {
mManager = new WeakReference<>(manager);
}
@Override
public void onEvent(CarHvacEvent event) {
CarHvacManager manager = mManager.get();
if (manager != null) {
manager.handleEvent(event);
}
}
}
/**
* Get an instance of the CarHvacManager.
*
* Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
* @hide
*/
public CarHvacManager(IBinder service, Context context, Looper looper) {
mService = ICarHvac.Stub.asInterface(service);
mHandler = new EventCallbackHandler(this, looper);
}
/** Returns true if the property is a zoned type. */
public static boolean isZonedProperty(int propertyId) {
return propertyId > HvacPropertyId.MAX_GLOBAL_PROPERTY_ID;
}
/**
* Register {@link CarHvacEventListener} to get HVAC property changes
*
* @param listener Implements onEvent() for property change updates
*/
public synchronized void registerListener(CarHvacEventListener listener)
throws CarNotConnectedException {
if(mListeners.isEmpty()) {
try {
mListenerToService = new CarHvacEventListenerToService(this);
mService.registerListener(mListenerToService);
} catch (RemoteException ex) {
Log.e(TAG, "Could not connect: " + ex.toString());
throw new CarNotConnectedException(ex);
} catch (IllegalStateException ex) {
Car.checkCarNotConnectedExceptionFromCarService(ex);
}
}
mListeners.add(listener);
}
/**
* Unregister {@link CarHvacEventListener}.
* @param listener CarHvacEventListener to unregister
*/
public synchronized void unregisterListener(CarHvacEventListener listener)
throws CarNotConnectedException {
if (DBG) {
Log.d(TAG, "unregisterListener");
}
try {
mService.unregisterListener(mListenerToService);
} catch (RemoteException e) {
Log.e(TAG, "Could not unregister: " + e.toString());
throw new CarNotConnectedException(e);
}
mListeners.remove(listener);
if(mListeners.isEmpty()) {
mListenerToService = null;
}
}
/**
* Returns the list of HVAC properties available.
*
* @return Caller must check the property type and typecast to the appropriate subclass
* (CarHvacBooleanProperty, CarHvacFloatProperty, CarrHvacIntProperty)
*/
public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException {
List<CarPropertyConfig> carProps;
try {
carProps = mService.getHvacProperties();
} catch (RemoteException e) {
Log.w(TAG, "Exception in getPropertyList", e);
throw new CarNotConnectedException(e);
}
return carProps;
}
/**
* 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;
}
@Nullable
@SuppressWarnings("unchecked")
private <E> CarPropertyValue<E> getProperty(Class<E> clazz, int prop, int area)
throws CarNotConnectedException {
if (DBG) {
Log.d(TAG, "getProperty, prop: 0x" + toHexString(prop)
+ ", area: 0x" + toHexString(area) + ", clazz: " + clazz);
}
try {
CarPropertyValue<E> hvacProperty = mService.getProperty(prop, area);
if (hvacProperty != null && hvacProperty.getValue() != null) {
Class<?> actualClass = hvacProperty.getValue().getClass();
if (actualClass != clazz) {
throw new IllegalArgumentException("Invalid property type. "
+ "Expected: " + clazz + ", but was: " + actualClass);
}
}
return hvacProperty;
} catch (RemoteException e) {
Log.e(TAG, "getProperty failed with " + e.toString()
+ ", propId: 0x" + toHexString(prop) + ", area: 0x" + toHexString(area), 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 {
if (DBG) {
Log.d(TAG, "setBooleanProperty: prop = " + prop + " area = " + area + " val = " + val);
}
try {
mService.setProperty(new CarPropertyValue<>(prop, area, val));
} catch (RemoteException e) {
Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e);
throw new CarNotConnectedException(e);
}
}
public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException {
if (DBG) {
Log.d(TAG, "setFloatProperty: prop = " + prop + " area = " + area + " val = " + val);
}
try {
mService.setProperty(new CarPropertyValue<>(prop, area, val));
} catch (RemoteException e) {
Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e);
throw new CarNotConnectedException(e);
}
}
public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException {
if (DBG) {
Log.d(TAG, "setIntProperty: prop = " + prop + " area = " + area + " val = " + val);
}
try {
mService.setProperty(new CarPropertyValue<>(prop, area, val));
} catch (RemoteException e) {
Log.e(TAG, "setIntProperty failed with " + e.toString(), e);
throw new CarNotConnectedException(e);
}
}
private void dispatchEventToClient(CarHvacEvent event) {
Collection<CarHvacEventListener> listeners;
synchronized (this) {
listeners = mListeners;
}
if (!listeners.isEmpty()) {
CarPropertyValue hvacProperty = event.getCarPropertyValue();
switch(event.getEventType()) {
case CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE:
for (CarHvacEventListener l: listeners) {
l.onChangeEvent(hvacProperty);
}
case CarHvacEvent.HVAC_EVENT_ERROR:
for (CarHvacEventListener l: listeners) {
l.onErrorEvent(hvacProperty.getPropertyId(), hvacProperty.getAreaId());
}
break;
default:
throw new IllegalArgumentException();
}
} else {
Log.e(TAG, "Listener died, not dispatching event.");
}
}
private void handleEvent(CarHvacEvent event) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_HVAC_EVENT, event));
}
/** @hide */
@Override
public void onCarDisconnected() {
for(CarHvacEventListener l: mListeners) {
try {
unregisterListener(l);
} catch (CarNotConnectedException e) {
// Ignore, car is disconnecting.
}
}
}
}