CarHvacManager.java revision 235f8acd3cf83079ecd0f3e1b8368b0c9886de82
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 android.annotation.SystemApi;
20import android.car.Car;
21import android.car.CarManagerBase;
22import android.car.CarNotConnectedException;
23import android.car.VehicleZoneUtil;
24import android.content.Context;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.Message;
29import android.os.RemoteException;
30import android.util.Log;
31
32import java.lang.ref.WeakReference;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.List;
36
37/**
38 * API for controlling HVAC system in cars
39 * @hide
40 */
41@SystemApi
42public class CarHvacManager implements CarManagerBase {
43    public final static boolean DBG = true;
44    public final static String TAG = "CarHvacManager";
45
46    /**
47     * Define types of values that are available.  Boolean type will be overloaded as an int for
48     * binder calls, and unpacked inside the HvacManager.
49     */
50    public static final int PROPERTY_TYPE_BOOLEAN      = 0;
51    public static final int PROPERTY_TYPE_FLOAT        = 1;
52    public static final int PROPERTY_TYPE_INT          = 2;
53    public static final int PROPERTY_TYPE_INT_VECTOR   = 3;
54    public static final int PROPERTY_TYPE_FLOAT_VECTOR = 4;
55
56    /**
57     * Global HVAC properties.  There is only a single instance in a car.
58     * Global properties are in the range of 0-0x3FFF.
59     */
60    /**
61     * Mirror defrosters state, bool.
62     */
63    public static final int HVAC_MIRROR_DEFROSTER_ON     = 0x0001;
64    /**
65     * HVAC is in automatic mode, bool.
66     */
67    public static final int HVAC_AUTOMATIC_MODE_ON       = 0x0003;
68    /**
69     * Air recirculation is active, bool.
70     */
71    public static final int HVAC_AIR_RECIRCULATION_ON    = 0x0004;
72    /**
73     * Steering wheel temp:  negative values indicate cooling, positive values indicate heat, int.
74     */
75    public static final int HVAC_STEERING_WHEEL_TEMP     = 0x0005;
76
77    /**
78     * The maximum id that can be assigned to global (non-zoned) property.
79     */
80    public static final int MAX_GLOBAL_PROPERTY_ID       = 0x3fff;
81
82    /**
83     * HVAC_ZONED_* represents properties available on a per-zone basis.  All zones in a car are
84     * are not required to have the same properties.  Zone specific properties start at 0x4000 and
85     * above.
86     *
87     * Temperature setpoint desired by the user, in terms of F or C, depending on TEMP_IS_FARENHEIT.
88     * If temp is celsius, the format is 31.1 (i.e. LSB = 0.5C).  int.
89     */
90    public static final int HVAC_ZONED_TEMP_SETPOINT             = 0x4001;
91    /**
92     * Actual zone temperature is read only integer, in terms of F or C, int.
93     */
94    public static final int HVAC_ZONED_TEMP_ACTUAL               = 0x4002;
95    /**
96     * Temperature is in degrees fahrenheit if this is true, bool.
97     */
98    public static final int HVAC_ZONED_TEMP_IS_FAHRENHEIT        = 0x4003;
99    /**
100     * Fan speed setpoint is an integer from 0-n, depending on the number of fan speeds available.
101     * Selection determines the fan position, int.
102     */
103    public static final int HVAC_ZONED_FAN_SPEED_SETPOINT        = 0x4004;
104    /**
105     * Actual fan speed is a read-only value, expressed in RPM, int.
106     */
107    public static final int HVAC_ZONED_FAN_SPEED_RPM             = 0x4005;
108    /**
109     * Fan position available is a bitmask of positions available for each zone, int.
110     */
111    public static final int HVAC_ZONED_FAN_POSITION_AVAILABLE    = 0x4006;
112    /**
113     * Current fan position setting, int.
114     */
115    public static final int HVAC_ZONED_FAN_POSITION              = 0x4007;
116    /**
117     * Seat temperature is negative for cooling, positive for heating.  Temperature is a setting,
118     * i.e. -3 to 3 for 3 levels of cooling and 3 levels of heating.  int.
119     */
120    public static final int HVAC_ZONED_SEAT_TEMP                 = 0x4008;
121    /**
122     * Air conditioner state, bool
123     */
124    public static final int HVAC_ZONED_AC_ON                   = 0x4009;
125    /**
126     * Defroster is based off of window position
127     */
128    public static final int HVAC_WINDOW_DEFROSTER_ON             = 0x5001;
129
130    // Minimum supported version of the service.
131    private static final int MIN_SUPPORTED_VERSION = 1;
132
133    // Minimum supported version of the callback.
134    private static final int MIN_SUPPORTED_CALLBACK_VERSION = 1;
135
136    // Constants handled in the handler (see mHandler below).
137    private final static int MSG_HVAC_EVENT = 0;
138
139    public static class CarHvacBaseProperty {
140        protected final int mPropertyId;
141        protected final int mType;
142        protected final int mZones;
143
144        public CarHvacBaseProperty(int propId, int type, int zones) {
145            mPropertyId = propId;
146            mType       = type;
147            mZones       = zones;
148        }
149
150        public int getPropertyId() {
151            return mPropertyId;
152        }
153
154        public int getType() {
155            return mType;
156        }
157
158        /**
159         * Tells if the given property is zoned property or global property
160         */
161        public boolean isZonedProperty() {
162            return mPropertyId > MAX_GLOBAL_PROPERTY_ID;
163        }
164
165        /**
166         * Return bit flags of supported zones.
167         */
168        public int getZones()       { return mZones; }
169
170        /**
171         * Return an active zone for Hvac event. This will return only one zone.
172         * If there is no valid zone, this will return 0.
173         */
174        public int getZone() {
175            if (mZones == 0) {
176                return 0;
177            }
178            int flag = 0x1;
179            for (int i = 0; i < 32; i++) {
180                if ((flag & mZones) != 0) {
181                    return flag;
182                }
183                flag <<= 1;
184            }
185            return 0;
186        }
187
188        @Override
189        public String toString() {
190            return "CarHvacBaseProperty [mPropertyId=0x" + Integer.toHexString(mPropertyId) +
191                    ", mType=0x" + Integer.toHexString(mType) +
192                    ", mZones=0x" + Integer.toHexString(mZones) + "]";
193        }
194
195        protected void assertZonedProperty() {
196            if (!isZonedProperty()) {
197                throw new IllegalArgumentException(
198                        "assertZonedProperty called for non-zoned property 0x" +
199                                Integer.toHexString(mPropertyId));
200            }
201        }
202
203        protected void assertNonZonedProperty() {
204            if (isZonedProperty()) {
205                throw new IllegalArgumentException(
206                        "assertNonZonedProperty called for zoned property 0x" +
207                        Integer.toHexString(mPropertyId));
208            }
209        }
210    }
211
212    public static final class CarHvacBooleanProperty extends CarHvacBaseProperty {
213        public CarHvacBooleanProperty(int propId, int zone) {
214            super(propId, PROPERTY_TYPE_BOOLEAN, zone);
215        }
216    }
217
218    public static final class CarHvacFloatProperty extends CarHvacBaseProperty {
219        private final float[] mMaxValues;
220        private final float[] mMinValues;
221
222        public CarHvacFloatProperty(int propId, int zones, float[] maxs, float mins[]) {
223            super(propId, PROPERTY_TYPE_FLOAT, zones);
224            int expectedLength = zones == 0 ? 1 : VehicleZoneUtil.getNumberOfZones(zones);
225            if (maxs.length != expectedLength || mins.length != expectedLength) {
226                throw new IllegalArgumentException("Expected length:" + expectedLength +
227                        " while maxs length:" + maxs.length + " mins length:" + mins.length +
228                        " property:0x" + Integer.toHexString(propId));
229            }
230            mMaxValues      = maxs;
231            mMinValues      = mins;
232        }
233
234        /**
235         * Get max value. Should be used only for non-zoned property.
236         */
237        public float getMaxValue() {
238            assertNonZonedProperty();
239            return mMaxValues[0];
240        }
241
242        /**
243         * Get min value. Should be used only for non-zoned property.
244         */
245        public float getMinValue() {
246            assertNonZonedProperty();
247            return mMinValues[0];
248        }
249
250        public float getMaxValue(int zone) {
251            assertZonedProperty();
252            return mMaxValues[VehicleZoneUtil.zoneToIndex(mZones, zone)];
253        }
254
255        public float getMinValue(int zone) {
256            assertZonedProperty();
257            return mMinValues[VehicleZoneUtil.zoneToIndex(mZones, zone)];
258        }
259
260        @Override
261        public String toString() {
262            return "CarHvacFloatProperty [mMaxValues=" + Arrays.toString(mMaxValues)
263                    + ", mMinValues=" + Arrays.toString(mMinValues) + " " + super.toString() + "]";
264        }
265    }
266
267    public static final class CarHvacIntProperty extends CarHvacBaseProperty {
268        private int[] mMaxValues;
269        private int[] mMinValues;
270
271        public CarHvacIntProperty(int propId, int zones, int[] maxs, int[] mins) {
272            super(propId, PROPERTY_TYPE_INT, zones);
273            int expectedLength = zones == 0 ? 1 : VehicleZoneUtil.getNumberOfZones(zones);
274            if (maxs.length != expectedLength || mins.length != expectedLength) {
275                throw new IllegalArgumentException("Expected length:" + expectedLength +
276                        " while maxs length:" + maxs.length + " mins length:" + mins.length +
277                        " property:0x" + Integer.toHexString(propId));
278            }
279            mMaxValues      = maxs;
280            mMinValues      = mins;
281        }
282
283        /**
284         * Get max value. Should be used only for non-zoned property.
285         */
286        public int getMaxValue() {
287            assertNonZonedProperty();
288            return mMaxValues[0];
289        }
290
291        /**
292         * Get min value. Should be used only for non-zoned property.
293         */
294        public int getMinValue() {
295            assertNonZonedProperty();
296            return mMinValues[0];
297        }
298
299        public int getMaxValue(int zone) {
300            assertZonedProperty();
301            return mMaxValues[VehicleZoneUtil.zoneToIndex(mZones, zone)];
302        }
303
304        public int getMinValue(int zone) {
305            assertZonedProperty();
306            return mMinValues[VehicleZoneUtil.zoneToIndex(mZones, zone)];
307        }
308
309        @Override
310        public String toString() {
311            return "CarHvacIntProperty [mMaxValues=" + Arrays.toString(mMaxValues)
312                    + ", mMinValues=" + Arrays.toString(mMinValues) + " " + super.toString() + "]";
313        }
314    }
315
316    public static final class CarHvacBooleanValue extends CarHvacBaseProperty {
317        private boolean mValue;
318
319        public CarHvacBooleanValue(int propId, int zones, boolean value) {
320            super(propId, PROPERTY_TYPE_BOOLEAN, zones);
321            mValue = value;
322        }
323
324        public boolean getValue() { return mValue; }
325
326        @Override
327        public String toString() {
328            return "CarHvacBooleanValue [mValue=" + mValue + " " + super.toString() + "]";
329        }
330    }
331
332
333    public static final class CarHvacFloatValue extends CarHvacBaseProperty {
334        private float mValue;
335
336        public CarHvacFloatValue(int propId, int zones, float value) {
337            super(propId, PROPERTY_TYPE_FLOAT, zones);
338            mValue = value;
339        }
340
341        public float getValue() { return mValue; }
342
343        @Override
344        public String toString() {
345            return "CarHvacFloatValue [mValue=" + mValue + " " + super.toString() + "]";
346        }
347    }
348
349    public static final class CarHvacIntValue extends CarHvacBaseProperty {
350        private int mValue;
351
352        public CarHvacIntValue(int propId, int zones, int value) {
353            super(propId, PROPERTY_TYPE_INT, zones);
354            mValue = value;
355        }
356
357        public int getValue() { return mValue; }
358
359        @Override
360        public String toString() {
361            return "CarHvacIntValue [mValue=" + mValue + " " + super.toString() + "]";
362        }
363    }
364
365    public interface CarHvacEventListener {
366        // Called when an HVAC property is updated
367        void onChangeEvent(final CarHvacBaseProperty value);
368
369        // Called when an error is detected with a property
370        void onErrorEvent(final int propertyId, final int zone);
371    }
372
373    private final ICarHvac mService;
374    private CarHvacEventListener mListener = null;
375    private CarHvacEventListenerToService mListenerToService = null;
376
377    private static final class EventCallbackHandler extends Handler {
378        WeakReference<CarHvacManager> mMgr;
379
380        EventCallbackHandler(CarHvacManager mgr, Looper looper) {
381            super(looper);
382            mMgr = new WeakReference<>(mgr);
383        }
384
385        @Override
386        public void handleMessage(Message msg) {
387            switch (msg.what) {
388                case MSG_HVAC_EVENT:
389                    CarHvacManager mgr = mMgr.get();
390                    if (mgr != null) {
391                        mgr.dispatchEventToClient((CarHvacEvent) msg.obj);
392                    }
393                    break;
394                default:
395                    Log.e(TAG, "Event type not handled?" + msg);
396                    break;
397            }
398        }
399    }
400
401    private final Handler mHandler;
402
403    private static class CarHvacEventListenerToService extends ICarHvacEventListener.Stub {
404        private final WeakReference<CarHvacManager> mManager;
405
406        public CarHvacEventListenerToService(CarHvacManager manager) {
407            mManager = new WeakReference<>(manager);
408        }
409
410        @Override
411        public void onEvent(CarHvacEvent event) {
412            CarHvacManager manager = mManager.get();
413            if (manager != null) {
414                manager.handleEvent(event);
415            }
416        }
417    }
418
419    /**
420     * Get an instance of the CarHvacManager.
421     *
422     * Should not be obtained directly by clients, use {@link Car.getCarManager()} instead.
423     * @hide
424     */
425    public CarHvacManager(IBinder service, Context context, Looper looper) {
426        mService = ICarHvac.Stub.asInterface(service);
427        mHandler = new EventCallbackHandler(this, looper);
428    }
429
430    public static boolean isZonedProperty(int propertyId) {
431        return propertyId > MAX_GLOBAL_PROPERTY_ID;
432    }
433
434    /**
435     * Register {@link CarHvacEventListener} to get HVAC property changes
436     *
437     * @param listener Implements onEvent() for property change updates
438     * @return
439     */
440    public synchronized void registerListener(CarHvacEventListener listener)
441            throws CarNotConnectedException {
442        if (mListener != null) {
443            throw new IllegalStateException("Listener already registered. Did you call " +
444                "registerListener() twice?");
445        }
446
447        mListener = listener;
448        try {
449            mListenerToService = new CarHvacEventListenerToService(this);
450            mService.registerListener(mListenerToService);
451        } catch (RemoteException e) {
452            Log.e(TAG, "Could not connect: " + e.toString());
453            mListener = null;
454            throw new CarNotConnectedException(e);
455        } catch (IllegalStateException e) {
456            Car.checkCarNotConnectedExceptionFromCarService(e);
457        }
458    }
459
460    /**
461     * Unregister {@link CarHvacEventListener}.
462     *
463     * @param
464     * @return
465     */
466    public synchronized void unregisterListener() throws CarNotConnectedException {
467        if (DBG) {
468            Log.d(TAG, "unregisterListener");
469        }
470        try {
471            mService.unregisterListener(mListenerToService);
472        } catch (RemoteException e) {
473            Log.e(TAG, "Could not unregister: " + e.toString());
474            throw new CarNotConnectedException(e);
475
476        }
477        mListenerToService = null;
478        mListener = null;
479    }
480
481    /**
482     * Returns the list of HVAC properties available.
483     *
484     * @return Caller must check the property type and typecast to the appropriate subclass
485     * (CarHvacBooleanProperty, CarHvacFloatProperty, CarrHvacIntProperty)
486     */
487    public List<CarHvacBaseProperty> getPropertyList() throws CarNotConnectedException {
488        List<CarHvacBaseProperty> hvacProps = new ArrayList<>();
489        List<CarHvacProperty> carProps;
490        try {
491            carProps = mService.getHvacProperties();
492        } catch (RemoteException e) {
493            Log.w(TAG, "Exception in getPropertyList", e);
494            throw new CarNotConnectedException(e);
495        }
496
497        for (CarHvacProperty carProp : carProps) {
498            switch (carProp.getType()) {
499                case PROPERTY_TYPE_BOOLEAN: {
500                    CarHvacBooleanProperty newProp =
501                            new CarHvacBooleanProperty(carProp.getPropertyId(), carProp.getZones());
502                    hvacProps.add(newProp);
503                } break;
504                case PROPERTY_TYPE_FLOAT: {
505                    CarHvacFloatProperty newProp =
506                            new CarHvacFloatProperty(carProp.getPropertyId(), carProp.getZones(),
507                                    carProp.getFloatMaxs(), carProp.getFloatMins());
508                    hvacProps.add(newProp);
509                } break;
510                case PROPERTY_TYPE_INT: {
511                    CarHvacIntProperty newProp =
512                            new CarHvacIntProperty(carProp.getPropertyId(), carProp.getZones(),
513                                    carProp.getIntMaxs(), carProp.getIntMins());
514                    hvacProps.add(newProp);
515                } break;
516            }
517        }
518        return hvacProps;
519    }
520
521    /**
522     * Returns value of a bool property
523     *
524     * @param prop Property ID to get
525     * @param zone Zone of the property to get
526     * @return
527     */
528    public boolean getBooleanProperty(int prop, int zone) throws CarNotConnectedException {
529        CarHvacProperty carProp;
530        if (DBG) {
531            Log.d(TAG, "getBooleanProperty:  prop = " + prop + " zone = " + zone);
532        }
533        try {
534            carProp = mService.getProperty(prop, zone);
535        } catch (RemoteException e) {
536            Log.e(TAG, "getProperty failed with " + e.toString());
537            throw new CarNotConnectedException(e);
538        }
539
540        if (carProp.getType() == PROPERTY_TYPE_BOOLEAN) {
541            return carProp.getBooleanValue();
542        } else {
543            throw new IllegalArgumentException();
544        }
545    }
546
547    /**
548     * Returns value of a float property
549     *
550     * @param prop Property ID to get
551     * @param zone Zone of the property to get
552     * @return
553     */
554    public float getFloatProperty(int prop, int zone) throws CarNotConnectedException {
555        CarHvacProperty carProp;
556        if (DBG) {
557            Log.d(TAG, "getFloatProperty:  prop = " + prop + " zone = " + zone);
558        }
559        try {
560            carProp = mService.getProperty(prop, zone);
561        } catch (RemoteException e) {
562            Log.e(TAG, "getProperty failed with " + e.toString());
563            throw new CarNotConnectedException(e);
564        }
565
566        if (carProp.getType() == PROPERTY_TYPE_FLOAT) {
567            return carProp.getFloatValue();
568        } else {
569            throw new IllegalArgumentException();
570        }
571    }
572
573    /**
574     * Returns value of a integer property
575     *
576     * @param prop Property ID to get
577     * @param zone Zone of the property to get
578     * @return
579     */
580    public int getIntProperty(int prop, int zone) throws CarNotConnectedException {
581        CarHvacProperty carProp;
582        if (DBG) {
583            Log.d(TAG, "getIntProperty:  prop = " + prop + " zone = " + zone);
584        }
585        try {
586            carProp = mService.getProperty(prop, zone);
587        } catch (RemoteException e) {
588            Log.e(TAG, "getProperty failed with " + e.toString());
589            throw new CarNotConnectedException(e);
590        }
591
592        if (carProp.getType() == PROPERTY_TYPE_INT) {
593            return carProp.getIntValue();
594        } else {
595            throw new IllegalArgumentException();
596        }
597    }
598
599
600    /**
601     * Modifies a property.  If the property modification doesn't occur, an error event shall be
602     * generated and propagated back to the application.
603     *
604     * @param prop Property ID to modify
605     * @param zone Zone(s) to apply the modification.  Multiple zones may be OR'd together
606     * @param val Value to set
607     */
608    public void setBooleanProperty(int prop, int zone, boolean val)
609            throws CarNotConnectedException {
610        if (DBG) {
611            Log.d(TAG, "setBooleanProperty:  prop = " + prop + " zone = " + zone + " val = " + val);
612        }
613        try {
614            CarHvacProperty carProp = new CarHvacProperty(prop, zone, val);
615            mService.setProperty(carProp);
616        } catch (RemoteException e) {
617            Log.e(TAG, "setBooleanProperty failed with " + e.toString());
618            throw new CarNotConnectedException(e);
619        }
620    }
621
622    public void setFloatProperty(int prop, int zone, float val) throws CarNotConnectedException {
623        if (DBG) {
624            Log.d(TAG, "setFloatProperty:  prop = " + prop + " zone = " + zone + " val = " + val);
625        }
626        try {
627            // Set floatMin and floatMax to 0, as they are ignored in set()
628            CarHvacProperty carProp = new CarHvacProperty(prop, zone,
629                    new float[] { 0 }, new float[] { 0 }, val);
630            mService.setProperty(carProp);
631        } catch (RemoteException e) {
632            Log.e(TAG, "setFloatProperty failed with " + e.toString());
633            throw new CarNotConnectedException(e);
634        }
635    }
636
637    public void setIntProperty(int prop, int zone, int val) throws CarNotConnectedException {
638        if (DBG) {
639            Log.d(TAG, "setIntProperty:  prop = " + prop + " zone = " + zone + " val = " + val);
640        }
641        try {
642            // Set intMin and intMax to 0, as they are ignored in set()
643            CarHvacProperty carProp = new CarHvacProperty(prop, zone,
644                    new int[] { 0 }, new int[] { 0 }, val);
645            mService.setProperty(carProp);
646        } catch (RemoteException e) {
647            Log.e(TAG, "setIntProperty failed with " + e.toString());
648            throw new CarNotConnectedException(e);
649        }
650    }
651
652    private void dispatchEventToClient(CarHvacEvent event) {
653        CarHvacEventListener listener = null;
654        synchronized (this) {
655            listener = mListener;
656        }
657        if (listener != null) {
658            int propertyId = event.getPropertyId();
659            int zone = event.getZone();
660
661            switch(event.getEventType()) {
662                case CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE:
663                    CarHvacBaseProperty value = null;
664                    switch(event.getPropertyType()) {
665                        case PROPERTY_TYPE_BOOLEAN: {
666                            value = new CarHvacBooleanValue(propertyId, zone,
667                                    event.getIntValue() == 1);
668                            break;
669                        }
670                        case PROPERTY_TYPE_FLOAT: {
671                            value = new CarHvacFloatValue(propertyId, zone, event.getFloatValue());
672                            break;
673                        }
674                        case PROPERTY_TYPE_INT: {
675                            value = new CarHvacIntValue(propertyId, zone, event.getIntValue());
676                            break;
677                        }
678                        default:
679                            throw new IllegalArgumentException();
680                    }
681                    listener.onChangeEvent(value);
682                    break;
683                case CarHvacEvent.HVAC_EVENT_ERROR:
684                    listener.onErrorEvent(propertyId, zone);
685                    break;
686                default:
687                    throw new IllegalArgumentException();
688            }
689        } else {
690            Log.e(TAG, "Listener died, not dispatching event.");
691        }
692    }
693
694    private void handleEvent(CarHvacEvent event) {
695        mHandler.sendMessage(mHandler.obtainMessage(MSG_HVAC_EVENT, event));
696    }
697
698    /** @hide */
699    @Override
700    public void onCarDisconnected() {
701        if (mListener != null) {
702            try {
703                unregisterListener();
704            } catch (CarNotConnectedException e) {
705                // ignore, as car is already disconnected.
706            }
707        }
708    }
709}
710