1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.car.hardware.hvac; 18 19 import static java.lang.Integer.toHexString; 20 21 import android.annotation.IntDef; 22 import android.annotation.Nullable; 23 import android.annotation.SystemApi; 24 import android.car.Car; 25 import android.car.CarManagerBase; 26 import android.car.CarNotConnectedException; 27 import android.car.hardware.CarPropertyConfig; 28 import android.car.hardware.CarPropertyValue; 29 import android.content.Context; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.RemoteException; 35 import android.util.ArraySet; 36 import android.util.Log; 37 38 import java.lang.ref.WeakReference; 39 import java.util.Collection; 40 import java.util.List; 41 42 /** 43 * API for controlling HVAC system in cars 44 * @hide 45 */ 46 @SystemApi 47 public class CarHvacManager implements CarManagerBase { 48 public final static boolean DBG = true; 49 public final static String TAG = "CarHvacManager"; 50 51 /** 52 * HVAC property IDs for get/set methods 53 */ 54 @IntDef({ 55 HvacPropertyId.MIRROR_DEFROSTER_ON, 56 HvacPropertyId.STEERING_WHEEL_TEMP, 57 HvacPropertyId.MAX_GLOBAL_PROPERTY_ID, 58 HvacPropertyId.ZONED_TEMP_SETPOINT, 59 HvacPropertyId.ZONED_TEMP_ACTUAL, 60 HvacPropertyId.ZONED_TEMP_IS_FAHRENHEIT, 61 HvacPropertyId.ZONED_FAN_SPEED_SETPOINT, 62 HvacPropertyId.ZONED_FAN_SPEED_RPM, 63 HvacPropertyId.ZONED_FAN_POSITION_AVAILABLE, 64 HvacPropertyId.ZONED_FAN_POSITION, 65 HvacPropertyId.ZONED_SEAT_TEMP, 66 HvacPropertyId.ZONED_AC_ON, 67 HvacPropertyId.ZONED_AUTOMATIC_MODE_ON, 68 HvacPropertyId.ZONED_AIR_RECIRCULATION_ON, 69 HvacPropertyId.WINDOW_DEFROSTER_ON, 70 }) 71 public @interface HvacPropertyId { 72 /** 73 * Global HVAC properties. There is only a single instance in a car. 74 * Global properties are in the range of 0-0x3FFF. 75 */ 76 /** Mirror defrosters state, bool. */ 77 int MIRROR_DEFROSTER_ON = 0x0001; 78 /** Steering wheel temp: negative values indicate cooling, positive values indicate 79 * heat, int. */ 80 int STEERING_WHEEL_TEMP = 0x0002; 81 82 /** The maximum id that can be assigned to global (non-zoned) property. */ 83 int MAX_GLOBAL_PROPERTY_ID = 0x3fff; 84 85 /** 86 * ZONED_* represents properties available on a per-zone basis. All zones in a car are 87 * not required to have the same properties. Zone specific properties start at 0x4000. 88 */ 89 /** Temperature setpoint desired by the user, in terms of F or C, depending on 90 * TEMP_IS_FAHRENHEIT, int */ 91 int ZONED_TEMP_SETPOINT = 0x4001; 92 /** Actual zone temperature is read only integer, in terms of F or C, int. */ 93 int ZONED_TEMP_ACTUAL = 0x4002; 94 /** Temperature is in degrees fahrenheit if this is true, bool. */ 95 int ZONED_TEMP_IS_FAHRENHEIT = 0x4003; 96 /** Fan speed setpoint is an integer from 0-n, depending on the number of fan speeds 97 * available. Selection determines the fan position, int. */ 98 int ZONED_FAN_SPEED_SETPOINT = 0x4004; 99 /** Actual fan speed is a read-only value, expressed in RPM, int. */ 100 int ZONED_FAN_SPEED_RPM = 0x4005; 101 /** Fan position available is a bitmask of positions available for each zone, int. */ 102 int ZONED_FAN_POSITION_AVAILABLE = 0x4006; 103 /** Current fan position setting, int. */ 104 int ZONED_FAN_POSITION = 0x4007; 105 /** Seat temperature is negative for cooling, positive for heating. Temperature is a 106 * setting, i.e. -3 to 3 for 3 levels of cooling and 3 levels of heating. int. */ 107 int ZONED_SEAT_TEMP = 0x4008; 108 /** Air conditioner state, bool */ 109 int ZONED_AC_ON = 0x4009; 110 /** HVAC is in automatic mode, bool. */ 111 int ZONED_AUTOMATIC_MODE_ON = 0x400A; 112 /** Air recirculation is active, bool. */ 113 int ZONED_AIR_RECIRCULATION_ON = 0x400B; 114 /** Defroster is based off of window position, bool */ 115 int WINDOW_DEFROSTER_ON = 0x5001; 116 } 117 118 // Constants handled in the handler (see mHandler below). 119 private final static int MSG_HVAC_EVENT = 0; 120 121 /** Callback functions for HVAC events */ 122 public interface CarHvacEventListener { 123 /** Called when an HVAC property is updated */ onChangeEvent(final CarPropertyValue value)124 void onChangeEvent(final CarPropertyValue value); 125 126 /** Called when an error is detected with a property */ onErrorEvent(final int propertyId, final int zone)127 void onErrorEvent(final int propertyId, final int zone); 128 } 129 130 private final ICarHvac mService; 131 private final ArraySet<CarHvacEventListener> mListeners = new ArraySet<>(); 132 private CarHvacEventListenerToService mListenerToService = null; 133 134 private static final class EventCallbackHandler extends Handler { 135 WeakReference<CarHvacManager> mMgr; 136 EventCallbackHandler(CarHvacManager mgr, Looper looper)137 EventCallbackHandler(CarHvacManager mgr, Looper looper) { 138 super(looper); 139 mMgr = new WeakReference<>(mgr); 140 } 141 142 @Override handleMessage(Message msg)143 public void handleMessage(Message msg) { 144 switch (msg.what) { 145 case MSG_HVAC_EVENT: 146 CarHvacManager mgr = mMgr.get(); 147 if (mgr != null) { 148 mgr.dispatchEventToClient((CarHvacEvent) msg.obj); 149 } 150 break; 151 default: 152 Log.e(TAG, "Event type not handled?" + msg); 153 break; 154 } 155 } 156 } 157 158 private final Handler mHandler; 159 160 private static class CarHvacEventListenerToService extends ICarHvacEventListener.Stub { 161 private final WeakReference<CarHvacManager> mManager; 162 CarHvacEventListenerToService(CarHvacManager manager)163 public CarHvacEventListenerToService(CarHvacManager manager) { 164 mManager = new WeakReference<>(manager); 165 } 166 167 @Override onEvent(CarHvacEvent event)168 public void onEvent(CarHvacEvent event) { 169 CarHvacManager manager = mManager.get(); 170 if (manager != null) { 171 manager.handleEvent(event); 172 } 173 } 174 } 175 176 /** 177 * Get an instance of the CarHvacManager. 178 * 179 * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead. 180 * @hide 181 */ CarHvacManager(IBinder service, Context context, Looper looper)182 public CarHvacManager(IBinder service, Context context, Looper looper) { 183 mService = ICarHvac.Stub.asInterface(service); 184 mHandler = new EventCallbackHandler(this, looper); 185 } 186 187 /** Returns true if the property is a zoned type. */ isZonedProperty(int propertyId)188 public static boolean isZonedProperty(int propertyId) { 189 return propertyId > HvacPropertyId.MAX_GLOBAL_PROPERTY_ID; 190 } 191 192 /** 193 * Register {@link CarHvacEventListener} to get HVAC property changes 194 * 195 * @param listener Implements onEvent() for property change updates 196 */ registerListener(CarHvacEventListener listener)197 public synchronized void registerListener(CarHvacEventListener listener) 198 throws CarNotConnectedException { 199 if(mListeners.isEmpty()) { 200 try { 201 mListenerToService = new CarHvacEventListenerToService(this); 202 mService.registerListener(mListenerToService); 203 } catch (RemoteException ex) { 204 Log.e(TAG, "Could not connect: " + ex.toString()); 205 throw new CarNotConnectedException(ex); 206 } catch (IllegalStateException ex) { 207 Car.checkCarNotConnectedExceptionFromCarService(ex); 208 } 209 } 210 mListeners.add(listener); 211 } 212 213 /** 214 * Unregister {@link CarHvacEventListener}. 215 * @param listener CarHvacEventListener to unregister 216 */ unregisterListener(CarHvacEventListener listener)217 public synchronized void unregisterListener(CarHvacEventListener listener) 218 throws CarNotConnectedException { 219 if (DBG) { 220 Log.d(TAG, "unregisterListener"); 221 } 222 try { 223 mService.unregisterListener(mListenerToService); 224 } catch (RemoteException e) { 225 Log.e(TAG, "Could not unregister: " + e.toString()); 226 throw new CarNotConnectedException(e); 227 228 } 229 mListeners.remove(listener); 230 if(mListeners.isEmpty()) { 231 mListenerToService = null; 232 } 233 } 234 235 /** 236 * Returns the list of HVAC properties available. 237 * 238 * @return Caller must check the property type and typecast to the appropriate subclass 239 * (CarHvacBooleanProperty, CarHvacFloatProperty, CarrHvacIntProperty) 240 */ getPropertyList()241 public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException { 242 List<CarPropertyConfig> carProps; 243 try { 244 carProps = mService.getHvacProperties(); 245 } catch (RemoteException e) { 246 Log.w(TAG, "Exception in getPropertyList", e); 247 throw new CarNotConnectedException(e); 248 } 249 return carProps; 250 } 251 252 /** 253 * Returns value of a bool property 254 * 255 * @param prop Property ID to get 256 * @param area Area of the property to get 257 */ getBooleanProperty(int prop, int area)258 public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException { 259 CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area); 260 return carProp != null ? carProp.getValue() : false; 261 } 262 263 /** 264 * Returns value of a float property 265 * 266 * @param prop Property ID to get 267 * @param area Area of the property to get 268 */ getFloatProperty(int prop, int area)269 public float getFloatProperty(int prop, int area) throws CarNotConnectedException { 270 CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area); 271 return carProp != null ? carProp.getValue() : 0f; 272 } 273 274 /** 275 * Returns value of a integer property 276 * 277 * @param prop Property ID to get 278 * @param area Zone of the property to get 279 */ getIntProperty(int prop, int area)280 public int getIntProperty(int prop, int area) throws CarNotConnectedException { 281 CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area); 282 return carProp != null ? carProp.getValue() : 0; 283 } 284 285 @Nullable 286 @SuppressWarnings("unchecked") getProperty(Class<E> clazz, int prop, int area)287 private <E> CarPropertyValue<E> getProperty(Class<E> clazz, int prop, int area) 288 throws CarNotConnectedException { 289 if (DBG) { 290 Log.d(TAG, "getProperty, prop: 0x" + toHexString(prop) 291 + ", area: 0x" + toHexString(area) + ", clazz: " + clazz); 292 } 293 try { 294 CarPropertyValue<E> hvacProperty = mService.getProperty(prop, area); 295 if (hvacProperty != null && hvacProperty.getValue() != null) { 296 Class<?> actualClass = hvacProperty.getValue().getClass(); 297 if (actualClass != clazz) { 298 throw new IllegalArgumentException("Invalid property type. " 299 + "Expected: " + clazz + ", but was: " + actualClass); 300 } 301 } 302 return hvacProperty; 303 } catch (RemoteException e) { 304 Log.e(TAG, "getProperty failed with " + e.toString() 305 + ", propId: 0x" + toHexString(prop) + ", area: 0x" + toHexString(area), e); 306 throw new CarNotConnectedException(e); 307 } 308 } 309 310 /** 311 * Modifies a property. If the property modification doesn't occur, an error event shall be 312 * generated and propagated back to the application. 313 * 314 * @param prop Property ID to modify 315 * @param area Area to apply the modification. 316 * @param val Value to set 317 */ setBooleanProperty(int prop, int area, boolean val)318 public void setBooleanProperty(int prop, int area, boolean val) 319 throws CarNotConnectedException { 320 if (DBG) { 321 Log.d(TAG, "setBooleanProperty: prop = " + prop + " area = " + area + " val = " + val); 322 } 323 try { 324 mService.setProperty(new CarPropertyValue<>(prop, area, val)); 325 } catch (RemoteException e) { 326 Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e); 327 throw new CarNotConnectedException(e); 328 } 329 } 330 setFloatProperty(int prop, int area, float val)331 public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException { 332 if (DBG) { 333 Log.d(TAG, "setFloatProperty: prop = " + prop + " area = " + area + " val = " + val); 334 } 335 try { 336 mService.setProperty(new CarPropertyValue<>(prop, area, val)); 337 } catch (RemoteException e) { 338 Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e); 339 throw new CarNotConnectedException(e); 340 } 341 } 342 setIntProperty(int prop, int area, int val)343 public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException { 344 if (DBG) { 345 Log.d(TAG, "setIntProperty: prop = " + prop + " area = " + area + " val = " + val); 346 } 347 try { 348 mService.setProperty(new CarPropertyValue<>(prop, area, val)); 349 } catch (RemoteException e) { 350 Log.e(TAG, "setIntProperty failed with " + e.toString(), e); 351 throw new CarNotConnectedException(e); 352 } 353 } 354 dispatchEventToClient(CarHvacEvent event)355 private void dispatchEventToClient(CarHvacEvent event) { 356 Collection<CarHvacEventListener> listeners; 357 synchronized (this) { 358 listeners = mListeners; 359 } 360 if (!listeners.isEmpty()) { 361 CarPropertyValue hvacProperty = event.getCarPropertyValue(); 362 switch(event.getEventType()) { 363 case CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE: 364 for (CarHvacEventListener l: listeners) { 365 l.onChangeEvent(hvacProperty); 366 } 367 case CarHvacEvent.HVAC_EVENT_ERROR: 368 for (CarHvacEventListener l: listeners) { 369 l.onErrorEvent(hvacProperty.getPropertyId(), hvacProperty.getAreaId()); 370 } 371 break; 372 default: 373 throw new IllegalArgumentException(); 374 } 375 } else { 376 Log.e(TAG, "Listener died, not dispatching event."); 377 } 378 } 379 handleEvent(CarHvacEvent event)380 private void handleEvent(CarHvacEvent event) { 381 mHandler.sendMessage(mHandler.obtainMessage(MSG_HVAC_EVENT, event)); 382 } 383 384 /** @hide */ 385 @Override onCarDisconnected()386 public void onCarDisconnected() { 387 for(CarHvacEventListener l: mListeners) { 388 try { 389 unregisterListener(l); 390 } catch (CarNotConnectedException e) { 391 // Ignore, car is disconnecting. 392 } 393 } 394 } 395 } 396