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