CarHvacManager.java revision 4fff3d5cc4112a341c331688bda9fcb65ed9016a
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.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 */
124        void onChangeEvent(final CarPropertyValue value);
125
126        /** Called when an error is detected with a property */
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
137        EventCallbackHandler(CarHvacManager mgr, Looper looper) {
138            super(looper);
139            mMgr = new WeakReference<>(mgr);
140        }
141
142        @Override
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
163        public CarHvacEventListenerToService(CarHvacManager manager) {
164            mManager = new WeakReference<>(manager);
165        }
166
167        @Override
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     */
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. */
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     */
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     */
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     */
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     */
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     */
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     */
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")
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     */
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
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
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
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
380    private void handleEvent(CarHvacEvent event) {
381        mHandler.sendMessage(mHandler.obtainMessage(MSG_HVAC_EVENT, event));
382    }
383
384    /** @hide */
385    @Override
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