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