1/*
2 * Copyright (c) 2016, 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 */
16package com.android.car.hvac;
17
18import android.app.Service;
19import android.car.VehicleSeat;
20import android.car.VehicleWindow;
21import android.car.VehicleZone;
22import android.car.hardware.CarPropertyConfig;
23import android.car.hardware.CarPropertyValue;
24import android.car.hardware.hvac.CarHvacManager;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.os.AsyncTask;
28import android.os.Binder;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.SystemProperties;
32import android.support.car.Car;
33import android.support.car.CarNotConnectedException;
34import android.support.car.CarConnectionCallback;
35import android.util.Log;
36
37import java.util.ArrayList;
38import java.util.List;
39
40import javax.annotation.concurrent.GuardedBy;
41
42public class HvacController extends Service {
43    private static final String DEMO_MODE_PROPERTY = "android.car.hvac.demo";
44    private static final String TAG = "HvacController";
45    private static final int DRIVER_ZONE_ID = VehicleSeat.SEAT_ROW_1_LEFT;
46    private static final int PASSENGER_ZONE_ID = VehicleSeat.SEAT_ROW_1_RIGHT;
47
48    public static final int[] AIRFLOW_STATES = new int[]{
49            CarHvacManager.FAN_POSITION_FACE,
50            CarHvacManager.FAN_POSITION_FLOOR,
51            CarHvacManager.FAN_POSITION_FACE_AND_FLOOR
52    };
53
54    /**
55     * Callback for receiving updates from the hvac manager. A Callback can be
56     * registered using {@link #registerCallback}.
57     */
58    public static abstract class Callback {
59
60        public void onPassengerTemperatureChange(float temp) {
61        }
62
63        public void onDriverTemperatureChange(float temp) {
64        }
65
66        public void onFanSpeedChange(int position) {
67        }
68
69        public void onAcStateChange(boolean isOn) {
70        }
71
72        public void onFrontDefrosterChange(boolean isOn) {
73        }
74
75        public void onRearDefrosterChange(boolean isOn) {
76        }
77
78        public void onPassengerSeatWarmerChange(int level) {
79        }
80
81        public void onDriverSeatWarmerChange(int level) {
82        }
83
84        public void onFanDirectionChange(int direction) {
85        }
86
87        public void onAirCirculationChange(boolean isOn) {
88        }
89
90        public void onAutoModeChange(boolean isOn) {
91        }
92
93        public void onHvacPowerChange(boolean isOn){
94        }
95    }
96
97    public class LocalBinder extends Binder {
98        HvacController getService() {
99            return HvacController.this;
100        }
101    }
102
103    private final Binder mBinder = new LocalBinder();
104
105    private Car mCarApiClient;
106    private CarHvacManager mHvacManager;
107    private Object mHvacManagerReady = new Object();
108
109    private HvacPolicy mPolicy;
110    @GuardedBy("mCallbacks")
111    private List<Callback> mCallbacks = new ArrayList<>();
112    private DataStore mDataStore = new DataStore();
113
114    @Override
115    public void onCreate() {
116        super.onCreate();
117        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
118            if (SystemProperties.getBoolean(DEMO_MODE_PROPERTY, false)) {
119                IBinder binder = (new LocalHvacPropertyService()).getCarPropertyService();
120                initHvacManager(new CarHvacManager(binder, this, new Handler()));
121                return;
122            }
123
124            mCarApiClient = Car.createCar(this, mCarConnectionCallback);
125            mCarApiClient.connect();
126        }
127    }
128
129    @Override
130    public void onDestroy() {
131        super.onDestroy();
132        if (mHvacManager != null) {
133            mHvacManager.unregisterCallback(mHardwareCallback);
134        }
135        if (mCarApiClient != null) {
136            mCarApiClient.disconnect();
137        }
138    }
139
140    @Override
141    public int onStartCommand(Intent intent, int flags, int startId) {
142        return START_STICKY;
143    }
144
145    @Override
146    public IBinder onBind(Intent intent) {
147        return mBinder;
148    }
149
150    public void registerCallback(Callback callback) {
151        synchronized (mCallbacks) {
152            mCallbacks.add(callback);
153        }
154    }
155
156    public void unregisterCallback(Callback callback) {
157        synchronized (mCallbacks) {
158            mCallbacks.remove(callback);
159        }
160    }
161
162    private void initHvacManager(CarHvacManager carHvacManager) {
163        mHvacManager = carHvacManager;
164        List<CarPropertyConfig> properties = null;
165        try {
166            properties = mHvacManager.getPropertyList();
167            mPolicy = new HvacPolicy(HvacController.this, properties);
168            mHvacManager.registerCallback(mHardwareCallback);
169        } catch (android.car.CarNotConnectedException e) {
170            Log.e(TAG, "Car not connected in HVAC");
171        }
172
173    }
174
175    private final CarConnectionCallback mCarConnectionCallback =
176            new CarConnectionCallback() {
177                @Override
178                public void onConnected(Car car) {
179                    synchronized (mHvacManagerReady) {
180                        try {
181                            initHvacManager((CarHvacManager) mCarApiClient.getCarManager(
182                                    android.car.Car.HVAC_SERVICE));
183                            mHvacManagerReady.notifyAll();
184                        } catch (CarNotConnectedException e) {
185                            Log.e(TAG, "Car not connected in onServiceConnected");
186                        }
187                    }
188                }
189
190                @Override
191                public void onDisconnected(Car car) {
192                }
193            };
194
195    private final CarHvacManager.CarHvacEventCallback mHardwareCallback =
196            new CarHvacManager.CarHvacEventCallback() {
197                @Override
198                public void onChangeEvent(final CarPropertyValue val) {
199                    int areaId = val.getAreaId();
200                    switch (val.getPropertyId()) {
201                        case CarHvacManager.ID_ZONED_AC_ON:
202                            handleAcStateUpdate(getValue(val));
203                            break;
204                        case CarHvacManager.ID_ZONED_FAN_POSITION:
205                            handleFanPositionUpdate(areaId, getValue(val));
206                            break;
207                        case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
208                            handleFanSpeedUpdate(areaId, getValue(val));
209                            break;
210                        case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
211                            handleTempUpdate(areaId, getValue(val));
212                            break;
213                        case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
214                            handleDefrosterUpdate(areaId, getValue(val));
215                            break;
216                        case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
217                            handleAirCirculationUpdate(getValue(val));
218                            break;
219                        case CarHvacManager.ID_ZONED_SEAT_TEMP:
220                            handleSeatWarmerUpdate(areaId, getValue(val));
221                            break;
222                        case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
223                            handleAutoModeUpdate(getValue(val));
224                            break;
225                        case CarHvacManager.ID_ZONED_HVAC_POWER_ON:
226                            handleHvacPowerOn(getValue(val));
227                            break;
228                        default:
229                            if (Log.isLoggable(TAG, Log.DEBUG)) {
230                                Log.d(TAG, "Unhandled HVAC event, id: " + val.getPropertyId());
231                            }
232                    }
233                }
234
235                @Override
236                public void onErrorEvent(final int propertyId, final int zone) {
237                }
238            };
239
240    @SuppressWarnings("unchecked")
241    public static <E> E getValue(CarPropertyValue propertyValue) {
242        return (E) propertyValue.getValue();
243    }
244
245    void handleHvacPowerOn(boolean isOn) {
246        boolean shouldPropagate = mDataStore.shouldPropagateHvacPowerUpdate(isOn);
247        if (Log.isLoggable(TAG, Log.DEBUG)) {
248            Log.d(TAG, "Hvac Power On: " + isOn + " should propagate: " + shouldPropagate);
249        }
250        if (shouldPropagate) {
251            synchronized (mCallbacks) {
252                for (int i = 0; i < mCallbacks.size(); i++) {
253                    mCallbacks.get(i).onHvacPowerChange(isOn);
254                }
255            }
256        }
257    }
258
259    void handleSeatWarmerUpdate(int zone, int level) {
260        boolean shouldPropagate = mDataStore.shouldPropagateSeatWarmerLevelUpdate(zone, level);
261        if (Log.isLoggable(TAG, Log.DEBUG)) {
262            Log.d(TAG, "Seat Warmer Update, zone: " + zone + " level: " + level +
263                    " should propagate: " + shouldPropagate);
264        }
265        if (shouldPropagate) {
266            synchronized (mCallbacks) {
267                for (int i = 0; i < mCallbacks.size(); i++) {
268                    if (zone == VehicleZone.ZONE_ROW_1_LEFT) {
269                        mCallbacks.get(i).onDriverSeatWarmerChange(level);
270                    } else {
271                        mCallbacks.get(i).onPassengerSeatWarmerChange(level);
272                    }
273                }
274            }
275        }
276    }
277
278    private void handleAirCirculationUpdate(boolean airCirculationState) {
279        boolean shouldPropagate
280                = mDataStore.shouldPropagateAirCirculationUpdate(airCirculationState);
281        if (Log.isLoggable(TAG, Log.DEBUG)) {
282            Log.d(TAG, "Air Circulation Update: " + airCirculationState +
283                    " should propagate: " + shouldPropagate);
284        }
285        if (shouldPropagate) {
286            synchronized (mCallbacks) {
287                for (int i = 0; i < mCallbacks.size(); i++) {
288                    mCallbacks.get(i).onAirCirculationChange(airCirculationState);
289                }
290            }
291        }
292    }
293
294    private void handleAutoModeUpdate(boolean autoModeState) {
295        boolean shouldPropagate = mDataStore.shouldPropagateAutoModeUpdate(autoModeState);
296        if (Log.isLoggable(TAG, Log.DEBUG)) {
297            Log.d(TAG, "AutoMode Update, id: " + autoModeState +
298                    " should propagate: " + shouldPropagate);
299        }
300        if (shouldPropagate) {
301            synchronized (mCallbacks) {
302                for (int i = 0; i < mCallbacks.size(); i++) {
303                    mCallbacks.get(i).onAutoModeChange(autoModeState);
304                }
305            }
306        }
307    }
308
309    private void handleAcStateUpdate(boolean acState) {
310        boolean shouldPropagate = mDataStore.shouldPropagateAcUpdate(acState);
311        if (Log.isLoggable(TAG, Log.DEBUG)) {
312            Log.d(TAG, "AC State Update, id: " + acState +
313                    " should propagate: " + shouldPropagate);
314        }
315        if (shouldPropagate) {
316            synchronized (mCallbacks) {
317                for (int i = 0; i < mCallbacks.size(); i++) {
318                    mCallbacks.get(i).onAcStateChange(acState);
319                }
320            }
321        }
322    }
323
324    private void handleFanPositionUpdate(int zone, int position) {
325        int index = fanPositionToAirflowIndex(position);
326        boolean shouldPropagate = mDataStore.shouldPropagateFanPositionUpdate(zone, index);
327        if (Log.isLoggable(TAG, Log.DEBUG)) {
328            Log.d(TAG, "Fan Position Update, zone: " + zone + " position: " + position +
329                    " should propagate: " + shouldPropagate);
330        }
331        if (shouldPropagate) {
332            synchronized (mCallbacks) {
333                for (int i = 0; i < mCallbacks.size(); i++) {
334                    mCallbacks.get(i).onFanDirectionChange(position);
335                }
336            }
337        }
338    }
339
340    private void handleFanSpeedUpdate(int zone, int speed) {
341        boolean shouldPropagate = mDataStore.shouldPropagateFanSpeedUpdate(zone, speed);
342        if (Log.isLoggable(TAG, Log.DEBUG)) {
343            Log.d(TAG, "Fan Speed Update, zone: " + zone + " speed: " + speed +
344                    " should propagate: " + shouldPropagate);
345        }
346        if (shouldPropagate) {
347            synchronized (mCallbacks) {
348                for (int i = 0; i < mCallbacks.size(); i++) {
349                    mCallbacks.get(i).onFanSpeedChange(speed);
350                }
351            }
352        }
353    }
354
355    private void handleTempUpdate(int zone, float temp) {
356        boolean shouldPropagate = mDataStore.shouldPropagateTempUpdate(zone, temp);
357        if (Log.isLoggable(TAG, Log.DEBUG)) {
358            Log.d(TAG, "Temp Update, zone: " + zone + " temp: " + temp +
359                    " should propagate: " + shouldPropagate);
360        }
361        if (shouldPropagate) {
362            int userTemperature =  mPolicy.hardwareToUserTemp(temp);
363            synchronized (mCallbacks) {
364                for (int i = 0; i < mCallbacks.size(); i++) {
365                    if (zone == VehicleZone.ZONE_ROW_1_LEFT) {
366                        mCallbacks.get(i)
367                                .onDriverTemperatureChange(userTemperature);
368                    } else {
369                        mCallbacks.get(i)
370                                .onPassengerTemperatureChange(userTemperature);
371                    }
372                }
373            }
374        }
375    }
376
377    private void handleDefrosterUpdate(int zone, boolean defrosterState) {
378        boolean shouldPropagate = mDataStore.shouldPropagateDefrosterUpdate(zone, defrosterState);
379        if (Log.isLoggable(TAG, Log.DEBUG)) {
380            Log.d(TAG, "Defroster Update, zone: " + zone + " state: " + defrosterState +
381                    " should propagate: " + shouldPropagate);
382        }
383        if (shouldPropagate) {
384            synchronized (mCallbacks) {
385                for (int i = 0; i < mCallbacks.size(); i++) {
386                    if (zone == VehicleWindow.WINDOW_FRONT_WINDSHIELD) {
387                        mCallbacks.get(i).onFrontDefrosterChange(defrosterState);
388                    } else if (zone == VehicleWindow.WINDOW_REAR_WINDSHIELD) {
389                        mCallbacks.get(i).onRearDefrosterChange(defrosterState);
390                    }
391                }
392            }
393        }
394    }
395
396    public void requestRefresh(final Runnable r, final Handler h) {
397        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
398            @Override
399            protected Void doInBackground(Void... unused) {
400                synchronized (mHvacManagerReady) {
401                    while (mHvacManager == null) {
402                        try {
403                            mHvacManagerReady.wait();
404                        } catch (InterruptedException e) {
405                            // We got interrupted so we might be shutting down.
406                            return null;
407                        }
408                    }
409                }
410                fetchTemperature(DRIVER_ZONE_ID);
411                fetchTemperature(PASSENGER_ZONE_ID);
412                fetchFanSpeed();
413                fetchDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD);
414                fetchDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD);
415                fetchAirflow(DRIVER_ZONE_ID);
416                fetchAirflow(PASSENGER_ZONE_ID);
417                fetchAcState();
418                fetchAirCirculation();
419                fetchHvacPowerState();
420                return null;
421            }
422
423            @Override
424            protected void onPostExecute(Void unused) {
425                h.post(r);
426            }
427        };
428        task.execute();
429    }
430
431    public HvacPolicy getPolicy() {
432        return mPolicy;
433    }
434
435    private void fetchTemperature(int zone) {
436        if (mHvacManager != null) {
437            try {
438                mDataStore.setTemperature(zone, mHvacManager.getFloatProperty(
439                        CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone));
440            } catch (android.car.CarNotConnectedException e) {
441                Log.e(TAG, "Car not connected in fetchTemperature");
442            }
443        }
444    }
445
446    public int getDriverTemperature() {
447        return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(DRIVER_ZONE_ID));
448    }
449
450    public int getPassengerTemperature() {
451        return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(PASSENGER_ZONE_ID));
452    }
453
454    public void setDriverTemperature(int temperature) {
455        setTemperature(DRIVER_ZONE_ID, mPolicy.userToHardwareTemp(temperature));
456    }
457
458    public void setPassengerTemperature(int temperature) {
459        setTemperature(PASSENGER_ZONE_ID, mPolicy.userToHardwareTemp(temperature));
460    }
461
462    public void setTemperature(final int zone, final float temperature) {
463        mDataStore.setTemperature(zone, temperature);
464        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
465            protected Void doInBackground(Void... unused) {
466                if (mHvacManager != null) {
467                    try {
468                        mHvacManager.setFloatProperty(
469                                CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone, temperature);
470                    } catch (android.car.CarNotConnectedException e) {
471                        Log.e(TAG, "Car not connected in setTemperature");
472                    }
473                }
474                return null;
475            }
476        };
477        task.execute();
478    }
479
480    public void setDriverSeatWarmerLevel(int level) {
481        setSeatWarmerLevel(DRIVER_ZONE_ID, level);
482    }
483
484    public void setPassengerSeatWarmerLevel(int level) {
485        setSeatWarmerLevel(PASSENGER_ZONE_ID, level);
486    }
487
488    public void setSeatWarmerLevel(final int zone, final int level) {
489        mDataStore.setSeatWarmerLevel(zone, level);
490        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
491            protected Void doInBackground(Void... unused) {
492                if (mHvacManager != null) {
493                    try {
494                        mHvacManager.setIntProperty(
495                                CarHvacManager.ID_ZONED_SEAT_TEMP, zone, level);
496                    } catch (android.car.CarNotConnectedException e) {
497                        Log.e(TAG, "Car not connected in setSeatWarmerLevel");
498                    }
499                }
500                return null;
501            }
502        };
503        task.execute();
504    }
505
506    private void fetchFanSpeed() {
507        if (mHvacManager != null) {
508            int zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
509            try {
510                mDataStore.setFanSpeed(mHvacManager.getIntProperty(
511                        CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone));
512            } catch (android.car.CarNotConnectedException e) {
513                Log.e(TAG, "Car not connected in fetchFanSpeed");
514            }
515        }
516    }
517
518    public int getFanSpeed() {
519        return mDataStore.getFanSpeed();
520    }
521
522    public void setFanSpeed(final int fanSpeed) {
523        mDataStore.setFanSpeed(fanSpeed);
524
525        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
526            int newFanSpeed;
527
528            protected Void doInBackground(Void... unused) {
529                if (mHvacManager != null) {
530                    int zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
531                    try {
532                        if (Log.isLoggable(TAG, Log.DEBUG)) {
533                            Log.d(TAG, "Setting fanspeed to: " + fanSpeed);
534                        }
535                        mHvacManager.setIntProperty(
536                                CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed);
537
538                        newFanSpeed = mHvacManager.getIntProperty(
539                                CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);
540                    } catch (android.car.CarNotConnectedException e) {
541                        Log.e(TAG, "Car not connected in setFanSpeed");
542                    }
543                }
544                return null;
545            }
546
547            @Override
548            protected void onPostExecute(final Void result) {
549                Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
550            }
551        };
552        task.execute();
553    }
554
555    private void fetchDefrosterState(int zone) {
556        if (mHvacManager != null) {
557            try {
558                mDataStore.setDefrosterState(zone, mHvacManager.getBooleanProperty(
559                        CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone));
560            } catch (android.car.CarNotConnectedException e) {
561                Log.e(TAG, "Car not connected in fetchDefrosterState");
562            }
563        }
564    }
565
566    public boolean getFrontDefrosterState() {
567        return mDataStore.getDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD);
568    }
569
570    public boolean getRearDefrosterState() {
571        return mDataStore.getDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD);
572    }
573
574    public void setFrontDefrosterState(boolean state) {
575        setDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD, state);
576    }
577
578    public void setRearDefrosterState(boolean state) {
579        setDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD, state);
580    }
581
582    public void setDefrosterState(final int zone, final boolean state) {
583        mDataStore.setDefrosterState(zone, state);
584        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
585            protected Void doInBackground(Void... unused) {
586                if (mHvacManager != null) {
587                    try {
588                        mHvacManager.setBooleanProperty(
589                                CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone, state);
590                    } catch (android.car.CarNotConnectedException e) {
591                        Log.e(TAG, "Car not connected in setDeforsterState");
592                    }
593                }
594                return null;
595            }
596        };
597        task.execute();
598    }
599
600    private void fetchAcState() {
601        if (mHvacManager != null) {
602            try {
603                mDataStore.setAcState(mHvacManager.getBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
604                        VehicleZone.ZONE_ROW_1_ALL));
605            } catch (android.car.CarNotConnectedException e) {
606                Log.e(TAG, "Car not connected in fetchAcState");
607            }
608        }
609    }
610
611    public boolean getAcState() {
612        return mDataStore.getAcState();
613    }
614
615    public void setAcState(final boolean state) {
616        mDataStore.setAcState(state);
617        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
618            protected Void doInBackground(Void... unused) {
619                if (mHvacManager != null) {
620                    try {
621                        mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
622                                VehicleZone.ZONE_ROW_1_ALL, state);
623                    } catch (android.car.CarNotConnectedException e) {
624                        Log.e(TAG, "Car not connected in setAcState");
625                    }
626                }
627                return null;
628            }
629        };
630        task.execute();
631    }
632
633    private int fanPositionToAirflowIndex(int fanPosition) {
634        for (int i = 0; i < AIRFLOW_STATES.length; i++) {
635            if (fanPosition == AIRFLOW_STATES[i]) {
636                return i;
637            }
638        }
639        Log.e(TAG, "Unknown fan position " + fanPosition + ". Returning default.");
640        return AIRFLOW_STATES[0];
641    }
642
643    private void fetchAirflow(int zone) {
644        if (mHvacManager != null) {
645            zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
646            try {
647                int val = mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_POSITION, zone);
648                mDataStore.setAirflow(zone, fanPositionToAirflowIndex(val));
649            } catch (android.car.CarNotConnectedException e) {
650                Log.e(TAG, "Car not connected in fetchAirFlow");
651            }
652        }
653    }
654
655    public int getAirflowIndex(int zone) {
656        return mDataStore.getAirflow(zone);
657    }
658
659    public void setAirflowIndex(final int zone, final int index) {
660        mDataStore.setAirflow(zone, index);
661        int override = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
662        int val = AIRFLOW_STATES[index];
663        setFanDirection(override, val);
664    }
665
666    public void setFanDirection(final int direction) {
667        mDataStore.setAirflow(VehicleZone.ZONE_ROW_1_ALL, direction);
668        setFanDirection(VehicleZone.ZONE_ROW_1_ALL, direction);
669    }
670
671    private void setFanDirection(final int zone, final int direction) {
672        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
673            protected Void doInBackground(Void... unused) {
674                if (mHvacManager != null) {
675                    try {
676                        mHvacManager.setIntProperty(
677                                CarHvacManager.ID_ZONED_FAN_POSITION, zone, direction);
678                    } catch (android.car.CarNotConnectedException e) {
679                        Log.e(TAG, "Car not connected in setAirflowIndex");
680                    }
681                }
682                return null;
683            }
684        };
685        task.execute();
686    }
687
688
689    private void fetchAirCirculation() {
690        if (mHvacManager != null) {
691            try {
692                mDataStore.setAirCirculationState(mHvacManager
693                        .getBooleanProperty(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
694                                VehicleZone.ZONE_ROW_1_ALL));
695            } catch (android.car.CarNotConnectedException e) {
696                Log.e(TAG, "Car not connected in fetchAirCirculationState");
697            }
698        }
699    }
700
701    public boolean getAirCirculationState() {
702        return mDataStore.getAirCirculationState();
703    }
704
705    public void setAirCirculation(final boolean state) {
706        mDataStore.setAirCirculationState(state);
707        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
708            protected Void doInBackground(Void... unused) {
709                if (mHvacManager != null) {
710                    try {
711                        mHvacManager.setBooleanProperty(
712                                CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
713                                VehicleZone.ZONE_ROW_1_ALL, state);
714                    } catch (android.car.CarNotConnectedException e) {
715                        Log.e(TAG, "Car not connected in setAcState");
716                    }
717                }
718                return null;
719            }
720        };
721        task.execute();
722    }
723
724    public boolean getAutoModeState() {
725        return mDataStore.getAutoModeState();
726    }
727
728    public void setAutoMode(final boolean state) {
729        mDataStore.setAutoModeState(state);
730        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
731            protected Void doInBackground(Void... unused) {
732                if (mHvacManager != null) {
733                    try {
734                        mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON,
735                                VehicleZone.ZONE_ROW_1_ALL, state);
736                    } catch (android.car.CarNotConnectedException e) {
737                        Log.e(TAG, "Car not connected in setAutoModeState");
738                    }
739                }
740                return null;
741            }
742        };
743        task.execute();
744    }
745
746    public boolean getHvacPowerState() {
747        return mDataStore.getHvacPowerState();
748    }
749
750    private void fetchHvacPowerState() {
751        if (mHvacManager != null) {
752            try {
753                mDataStore.setHvacPowerState(mHvacManager.getBooleanProperty(
754                        CarHvacManager.ID_ZONED_HVAC_POWER_ON, VehicleZone.ZONE_ROW_1_ALL));
755            } catch (android.car.CarNotConnectedException e) {
756                Log.e(TAG, "Car not connected in fetchHvacPowerState");
757            }
758        }
759    }
760}
761