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  */
16 package com.android.car.hvac;
17 
18 import android.app.Service;
19 import android.car.Car;
20 import android.car.CarNotConnectedException;
21 import android.car.VehicleAreaSeat;
22 import android.car.VehicleAreaWindow;
23 import android.car.hardware.CarPropertyConfig;
24 import android.car.hardware.CarPropertyValue;
25 import android.car.hardware.hvac.CarHvacManager;
26 import android.content.ComponentName;
27 import android.content.Intent;
28 import android.content.ServiceConnection;
29 import android.content.pm.PackageManager;
30 import android.os.AsyncTask;
31 import android.os.Binder;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.util.Log;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 import javax.annotation.concurrent.GuardedBy;
40 
41 public class HvacController extends Service {
42     private static final String TAG = "HvacController";
43     private static final int DRIVER_ZONE_ID = VehicleAreaSeat.SEAT_ROW_1_LEFT |
44             VehicleAreaSeat.SEAT_ROW_2_LEFT | VehicleAreaSeat.SEAT_ROW_2_CENTER;
45     private static final int PASSENGER_ZONE_ID = VehicleAreaSeat.SEAT_ROW_1_RIGHT |
46             VehicleAreaSeat.SEAT_ROW_2_RIGHT;
47 
48     public static final int[] AIRFLOW_STATES = new int[]{
49             CarHvacManager.FAN_DIRECTION_FACE,
50             CarHvacManager.FAN_DIRECTION_FLOOR,
51             (CarHvacManager.FAN_DIRECTION_FACE | CarHvacManager.FAN_DIRECTION_FLOOR)
52     };
53     // Hardware specific value for the front seats
54     public static final int SEAT_ALL = DRIVER_ZONE_ID | PASSENGER_ZONE_ID;
55 
56     /**
57      * Callback for receiving updates from the hvac manager. A Callback can be
58      * registered using {@link #registerCallback}.
59      */
60     public static abstract class Callback {
61 
onPassengerTemperatureChange(CarPropertyValue propValue)62         public void onPassengerTemperatureChange(CarPropertyValue propValue) {
63         }
64 
onDriverTemperatureChange(CarPropertyValue propValue)65         public void onDriverTemperatureChange(CarPropertyValue propValue) {
66         }
67 
onFanSpeedChange(int position)68         public void onFanSpeedChange(int position) {
69         }
70 
onAcStateChange(boolean isOn)71         public void onAcStateChange(boolean isOn) {
72         }
73 
onFrontDefrosterChange(boolean isOn)74         public void onFrontDefrosterChange(boolean isOn) {
75         }
76 
onRearDefrosterChange(boolean isOn)77         public void onRearDefrosterChange(boolean isOn) {
78         }
79 
onPassengerSeatWarmerChange(int level)80         public void onPassengerSeatWarmerChange(int level) {
81         }
82 
onDriverSeatWarmerChange(int level)83         public void onDriverSeatWarmerChange(int level) {
84         }
85 
onFanDirectionChange(int direction)86         public void onFanDirectionChange(int direction) {
87         }
88 
onAirCirculationChange(boolean isOn)89         public void onAirCirculationChange(boolean isOn) {
90         }
91 
onAutoModeChange(boolean isOn)92         public void onAutoModeChange(boolean isOn) {
93         }
94 
onHvacPowerChange(boolean isOn)95         public void onHvacPowerChange(boolean isOn) {
96         }
97     }
98 
99     public class LocalBinder extends Binder {
getService()100         HvacController getService() {
101             return HvacController.this;
102         }
103     }
104 
105     private final Binder mBinder = new LocalBinder();
106 
107     private Car mCarApiClient;
108     private CarHvacManager mHvacManager;
109     private Object mHvacManagerReady = new Object();
110 
111     private HvacPolicy mPolicy;
112     @GuardedBy("mCallbacks")
113     private List<Callback> mCallbacks = new ArrayList<>();
114     private DataStore mDataStore = new DataStore();
115 
116     @Override
onCreate()117     public void onCreate() {
118         super.onCreate();
119         if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
120             mCarApiClient = Car.createCar(this, mCarServiceConnection);
121             mCarApiClient.connect();
122         }
123     }
124 
125     @Override
onDestroy()126     public void onDestroy() {
127         super.onDestroy();
128         if (mHvacManager != null) {
129             mHvacManager.unregisterCallback(mHardwareCallback);
130         }
131         if (mCarApiClient != null) {
132             mCarApiClient.disconnect();
133         }
134     }
135 
136     @Override
onStartCommand(Intent intent, int flags, int startId)137     public int onStartCommand(Intent intent, int flags, int startId) {
138         return START_STICKY;
139     }
140 
141     @Override
onBind(Intent intent)142     public IBinder onBind(Intent intent) {
143         return mBinder;
144     }
145 
registerCallback(Callback callback)146     public void registerCallback(Callback callback) {
147         synchronized (mCallbacks) {
148             mCallbacks.add(callback);
149         }
150     }
151 
unregisterCallback(Callback callback)152     public void unregisterCallback(Callback callback) {
153         synchronized (mCallbacks) {
154             mCallbacks.remove(callback);
155         }
156     }
157 
initHvacManager(CarHvacManager carHvacManager)158     private void initHvacManager(CarHvacManager carHvacManager) {
159         mHvacManager = carHvacManager;
160         List<CarPropertyConfig> properties = null;
161         try {
162             properties = mHvacManager.getPropertyList();
163             mPolicy = new HvacPolicy(HvacController.this, properties);
164             mHvacManager.registerCallback(mHardwareCallback);
165         } catch (CarNotConnectedException e) {
166             Log.e(TAG, "Car not connected in HVAC");
167         }
168 
169     }
170 
171     private final ServiceConnection mCarServiceConnection =
172             new ServiceConnection() {
173                 @Override
174                 public void onServiceConnected(ComponentName name, IBinder service) {
175                     synchronized (mHvacManagerReady) {
176                         try {
177                             initHvacManager((CarHvacManager) mCarApiClient.getCarManager(
178                                     Car.HVAC_SERVICE));
179                             mHvacManagerReady.notifyAll();
180                         } catch (CarNotConnectedException e) {
181                             Log.e(TAG, "Car not connected in onServiceConnected");
182                         }
183                     }
184                 }
185 
186                 @Override
187                 public void onServiceDisconnected(ComponentName name) {
188                 }
189             };
190 
191     private final CarHvacManager.CarHvacEventCallback mHardwareCallback =
192             new CarHvacManager.CarHvacEventCallback() {
193                 @Override
194                 public void onChangeEvent(final CarPropertyValue val) {
195                     int areaId = val.getAreaId();
196                     switch (val.getPropertyId()) {
197                         case CarHvacManager.ID_ZONED_AC_ON:
198                             handleAcStateUpdate(getValue(val));
199                             break;
200                         case CarHvacManager.ID_ZONED_FAN_DIRECTION:
201                             handleFanPositionUpdate(areaId, getValue(val));
202                             break;
203                         case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
204                             handleFanSpeedUpdate(areaId, getValue(val));
205                             break;
206                         case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
207                             handleTempUpdate(val);
208                             break;
209                         case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
210                             handleDefrosterUpdate(areaId, getValue(val));
211                             break;
212                         case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
213                             handleAirCirculationUpdate(getValue(val));
214                             break;
215                         case CarHvacManager.ID_ZONED_SEAT_TEMP:
216                             handleSeatWarmerUpdate(areaId, getValue(val));
217                             break;
218                         case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
219                             handleAutoModeUpdate(getValue(val));
220                             break;
221                         case CarHvacManager.ID_ZONED_HVAC_POWER_ON:
222                             handleHvacPowerOn(getValue(val));
223                             break;
224                         default:
225                             if (Log.isLoggable(TAG, Log.DEBUG)) {
226                                 Log.d(TAG, "Unhandled HVAC event, id: " + val.getPropertyId());
227                             }
228                     }
229                 }
230 
231                 @Override
232                 public void onErrorEvent(final int propertyId, final int zone) {
233                 }
234             };
235 
236     @SuppressWarnings("unchecked")
getValue(CarPropertyValue propertyValue)237     public static <E> E getValue(CarPropertyValue propertyValue) {
238         return (E) propertyValue.getValue();
239     }
240 
isAvailable(CarPropertyValue propertyValue)241     public static boolean isAvailable(CarPropertyValue propertyValue) {
242         return propertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE;
243     }
244 
handleHvacPowerOn(boolean isOn)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 
handleSeatWarmerUpdate(int zone, int level)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 == VehicleAreaSeat.SEAT_ROW_1_LEFT) {
269                         mCallbacks.get(i).onDriverSeatWarmerChange(level);
270                     } else {
271                         mCallbacks.get(i).onPassengerSeatWarmerChange(level);
272                     }
273                 }
274             }
275         }
276     }
277 
handleAirCirculationUpdate(boolean airCirculationState)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 
handleAutoModeUpdate(boolean autoModeState)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 
handleAcStateUpdate(boolean acState)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 
handleFanPositionUpdate(int zone, int position)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 
handleFanSpeedUpdate(int zone, int speed)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 
handleTempUpdate(CarPropertyValue value)355     private void handleTempUpdate(CarPropertyValue value) {
356         final int zone = value.getAreaId();
357         final float temp = (Float) value.getValue();
358         final boolean available = value.getStatus() == CarPropertyValue.STATUS_AVAILABLE;
359         boolean shouldPropagate = mDataStore.shouldPropagateTempUpdate(zone, temp, available);
360         if (Log.isLoggable(TAG, Log.DEBUG)) {
361             Log.d(TAG, "Temp Update, zone: " + zone + " temp: " + temp +
362                     "available: " + available + " should propagate: " + shouldPropagate);
363         }
364         if (shouldPropagate) {
365             synchronized (mCallbacks) {
366                 for (int i = 0; i < mCallbacks.size(); i++) {
367                     if (zone == DRIVER_ZONE_ID) {
368                         mCallbacks.get(i)
369                                 .onDriverTemperatureChange(value);
370                     } else if (zone == PASSENGER_ZONE_ID) {
371                         mCallbacks.get(i)
372                                 .onPassengerTemperatureChange(value);
373                     } else {
374                         Log.w(TAG, "Unknown temperature set area id: " + zone);
375                     }
376                 }
377             }
378         }
379     }
380 
handleDefrosterUpdate(int zone, boolean defrosterState)381     private void handleDefrosterUpdate(int zone, boolean defrosterState) {
382         boolean shouldPropagate = mDataStore.shouldPropagateDefrosterUpdate(zone, defrosterState);
383         if (Log.isLoggable(TAG, Log.DEBUG)) {
384             Log.d(TAG, "Defroster Update, zone: " + zone + " state: " + defrosterState +
385                     " should propagate: " + shouldPropagate);
386         }
387         if (shouldPropagate) {
388             synchronized (mCallbacks) {
389                 for (int i = 0; i < mCallbacks.size(); i++) {
390                     if (zone == VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD) {
391                         mCallbacks.get(i).onFrontDefrosterChange(defrosterState);
392                     } else if (zone == VehicleAreaWindow.WINDOW_REAR_WINDSHIELD) {
393                         mCallbacks.get(i).onRearDefrosterChange(defrosterState);
394                     }
395                 }
396             }
397         }
398     }
399 
requestRefresh(final Runnable r, final Handler h)400     public void requestRefresh(final Runnable r, final Handler h) {
401         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
402             @Override
403             protected Void doInBackground(Void... unused) {
404                 synchronized (mHvacManagerReady) {
405                     while (mHvacManager == null) {
406                         try {
407                             mHvacManagerReady.wait();
408                         } catch (InterruptedException e) {
409                             // We got interrupted so we might be shutting down.
410                             return null;
411                         }
412                     }
413                 }
414                 fetchTemperature(DRIVER_ZONE_ID);
415                 fetchTemperature(PASSENGER_ZONE_ID);
416                 fetchFanSpeed();
417                 fetchDefrosterState(VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD);
418                 fetchDefrosterState(VehicleAreaWindow.WINDOW_REAR_WINDSHIELD);
419                 fetchAirflow();
420                 fetchAcState();
421                 fetchAirCirculation();
422                 fetchHvacPowerState();
423                 return null;
424             }
425 
426             @Override
427             protected void onPostExecute(Void unused) {
428                 h.post(r);
429             }
430         };
431         task.execute();
432     }
433 
getPolicy()434     public HvacPolicy getPolicy() {
435         return mPolicy;
436     }
437 
isTemperatureControlAvailable(int zone)438     public boolean isTemperatureControlAvailable(int zone) {
439         if (mHvacManager != null) {
440             try {
441                 return mHvacManager.isPropertyAvailable(
442                         CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone);
443             } catch (android.car.CarNotConnectedException e) {
444                 Log.e(TAG, "Car not connected in isTemperatureControlAvailable");
445             }
446         }
447 
448         return false;
449     }
450 
isDriverTemperatureControlAvailable()451     public boolean isDriverTemperatureControlAvailable() {
452         return isTemperatureControlAvailable(DRIVER_ZONE_ID);
453     }
454 
isPassengerTemperatureControlAvailable()455     public boolean isPassengerTemperatureControlAvailable() {
456         return isTemperatureControlAvailable(PASSENGER_ZONE_ID);
457     }
458 
fetchTemperature(int zone)459     private void fetchTemperature(int zone) {
460         if (mHvacManager != null) {
461             try {
462                 float value = mHvacManager.getFloatProperty(
463                         CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone);
464                 boolean available = mHvacManager.isPropertyAvailable(
465                         CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone);
466                 mDataStore.setTemperature(zone, value, available);
467             } catch (android.car.CarNotConnectedException e) {
468                 Log.e(TAG, "Car not connected in fetchTemperature");
469             }
470         }
471     }
472 
getDriverTemperature()473     public int getDriverTemperature() {
474         return Float.valueOf(mDataStore.getTemperature(DRIVER_ZONE_ID)).intValue();
475     }
476 
getPassengerTemperature()477     public int getPassengerTemperature() {
478         return Float.valueOf(mDataStore.getTemperature(PASSENGER_ZONE_ID)).intValue();
479     }
480 
setDriverTemperature(int temperature)481     public void setDriverTemperature(int temperature) {
482         setTemperature(DRIVER_ZONE_ID, temperature);
483     }
484 
setPassengerTemperature(int temperature)485     public void setPassengerTemperature(int temperature) {
486         setTemperature(PASSENGER_ZONE_ID, temperature);
487     }
488 
setTemperature(final int zone, final float temperature)489     public void setTemperature(final int zone, final float temperature) {
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.setFloatProperty(
495                                 CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone, temperature);
496                         // if the set() succeeds, consider the property available
497                         mDataStore.setTemperature(zone, temperature, true);
498                     } catch (android.car.CarNotConnectedException e) {
499                         Log.e(TAG, "Car not connected in setTemperature");
500                     } catch (Exception e) {
501                         Log.e(TAG, "set temp failed", e);
502                     }
503                 }
504                 return null;
505             }
506         };
507         task.execute();
508     }
509 
setHvacPowerState(final boolean state)510     public void setHvacPowerState(final boolean state) {
511         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
512             protected Void doInBackground(Void... unused) {
513                 if (mHvacManager != null) {
514                     try {
515                         mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_HVAC_POWER_ON,
516                                 SEAT_ALL, state);
517                         // if the set() succeeds, consider the property available
518                         mDataStore.setHvacPowerState(state);
519                     } catch (android.car.CarNotConnectedException e) {
520                         Log.e(TAG, "Car not connected in setHvacPowerState");
521                     } catch (Exception e) {
522                         Log.e(TAG, "set power failed", e);
523                     }
524                 }
525                 return null;
526             }
527         };
528         task.execute();
529     }
530 
setDriverSeatWarmerLevel(int level)531     public void setDriverSeatWarmerLevel(int level) {
532         setSeatWarmerLevel(VehicleAreaSeat.SEAT_ROW_1_LEFT, level);
533     }
534 
setPassengerSeatWarmerLevel(int level)535     public void setPassengerSeatWarmerLevel(int level) {
536         setSeatWarmerLevel(VehicleAreaSeat.SEAT_ROW_1_RIGHT, level);
537     }
538 
setSeatWarmerLevel(final int zone, final int level)539     public void setSeatWarmerLevel(final int zone, final int level) {
540         mDataStore.setSeatWarmerLevel(zone, level);
541         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
542             protected Void doInBackground(Void... unused) {
543                 if (mHvacManager != null) {
544                     try {
545                         mHvacManager.setIntProperty(
546                                 CarHvacManager.ID_ZONED_SEAT_TEMP, zone, level);
547                     } catch (android.car.CarNotConnectedException e) {
548                         Log.e(TAG, "Car not connected in setSeatWarmerLevel");
549                     } catch (Exception e) {
550                         Log.e(TAG, "set seat warmer failed", e);
551                     }
552                 }
553                 return null;
554             }
555         };
556         task.execute();
557     }
558 
fetchFanSpeed()559     private void fetchFanSpeed() {
560         if (mHvacManager != null) {
561             int zone = SEAT_ALL; // Car specific workaround.
562             try {
563                 mDataStore.setFanSpeed(mHvacManager.getIntProperty(
564                         CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone));
565             } catch (android.car.CarNotConnectedException e) {
566                 Log.e(TAG, "Car not connected in fetchFanSpeed");
567             }
568         }
569     }
570 
getFanSpeed()571     public int getFanSpeed() {
572         return mDataStore.getFanSpeed();
573     }
574 
setFanSpeed(final int fanSpeed)575     public void setFanSpeed(final int fanSpeed) {
576         mDataStore.setFanSpeed(fanSpeed);
577 
578         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
579             int newFanSpeed;
580 
581             protected Void doInBackground(Void... unused) {
582                 if (mHvacManager != null) {
583                     int zone = SEAT_ALL; // Car specific workaround.
584                     try {
585                         if (Log.isLoggable(TAG, Log.DEBUG)) {
586                             Log.d(TAG, "Setting fanspeed to: " + fanSpeed);
587                         }
588                         mHvacManager.setIntProperty(
589                                 CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed);
590 
591                         newFanSpeed = mHvacManager.getIntProperty(
592                                 CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);
593                     } catch (android.car.CarNotConnectedException e) {
594                         Log.e(TAG, "Car not connected in setFanSpeed");
595                     }
596                 }
597                 return null;
598             }
599 
600             @Override
601             protected void onPostExecute(final Void result) {
602                 Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
603             }
604         };
605         task.execute();
606     }
607 
fetchDefrosterState(int zone)608     private void fetchDefrosterState(int zone) {
609         if (mHvacManager != null) {
610             try {
611                 mDataStore.setDefrosterState(zone, mHvacManager.getBooleanProperty(
612                         CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone));
613             } catch (android.car.CarNotConnectedException e) {
614                 Log.e(TAG, "Car not connected in fetchDefrosterState");
615             }
616         }
617     }
618 
getFrontDefrosterState()619     public boolean getFrontDefrosterState() {
620         return mDataStore.getDefrosterState(VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD);
621     }
622 
getRearDefrosterState()623     public boolean getRearDefrosterState() {
624         return mDataStore.getDefrosterState(VehicleAreaWindow.WINDOW_REAR_WINDSHIELD);
625     }
626 
setFrontDefrosterState(boolean state)627     public void setFrontDefrosterState(boolean state) {
628         setDefrosterState(VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD, state);
629     }
630 
setRearDefrosterState(boolean state)631     public void setRearDefrosterState(boolean state) {
632         setDefrosterState(VehicleAreaWindow.WINDOW_REAR_WINDSHIELD, state);
633     }
634 
setDefrosterState(final int zone, final boolean state)635     public void setDefrosterState(final int zone, final boolean state) {
636         mDataStore.setDefrosterState(zone, state);
637         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
638             protected Void doInBackground(Void... unused) {
639                 if (mHvacManager != null) {
640                     try {
641                         mHvacManager.setBooleanProperty(
642                                 CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone, state);
643                     } catch (android.car.CarNotConnectedException e) {
644                         Log.e(TAG, "Car not connected in setDeforsterState");
645                     }
646                 }
647                 return null;
648             }
649         };
650         task.execute();
651     }
652 
fetchAcState()653     private void fetchAcState() {
654         if (mHvacManager != null) {
655             try {
656                 mDataStore.setAcState(mHvacManager.getBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
657                         SEAT_ALL));
658             } catch (android.car.CarNotConnectedException e) {
659                 Log.e(TAG, "Car not connected in fetchAcState");
660             }
661         }
662     }
663 
getAcState()664     public boolean getAcState() {
665         return mDataStore.getAcState();
666     }
667 
setAcState(final boolean state)668     public void setAcState(final boolean state) {
669         mDataStore.setAcState(state);
670         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
671             protected Void doInBackground(Void... unused) {
672                 if (mHvacManager != null) {
673                     try {
674                         mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
675                                 SEAT_ALL, state);
676                     } catch (android.car.CarNotConnectedException e) {
677                         Log.e(TAG, "Car not connected in setAcState");
678                     }
679                 }
680                 return null;
681             }
682         };
683         task.execute();
684     }
685 
fanPositionToAirflowIndex(int fanPosition)686     private int fanPositionToAirflowIndex(int fanPosition) {
687         for (int i = 0; i < AIRFLOW_STATES.length; i++) {
688             if (fanPosition == AIRFLOW_STATES[i]) {
689                 return i;
690             }
691         }
692         Log.e(TAG, "Unknown fan position " + fanPosition + ". Returning default.");
693         return AIRFLOW_STATES[0];
694     }
695 
fetchAirflow()696     private void fetchAirflow() {
697         if (mHvacManager != null) {
698             try {
699                 int val = mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_DIRECTION,
700                         SEAT_ALL);
701                 mDataStore.setAirflow(SEAT_ALL, fanPositionToAirflowIndex(val));
702             } catch (android.car.CarNotConnectedException e) {
703                 Log.e(TAG, "Car not connected in fetchAirFlow");
704             }
705         }
706     }
707 
getAirflowIndex(int zone)708     public int getAirflowIndex(int zone) {
709         return mDataStore.getAirflow(zone);
710     }
711 
setAirflowIndex(final int zone, final int index)712     public void setAirflowIndex(final int zone, final int index) {
713         mDataStore.setAirflow(zone, index);
714         int override = SEAT_ALL; // Car specific workaround.
715         int val = AIRFLOW_STATES[index];
716         setFanDirection(override, val);
717     }
718 
setFanDirection(final int direction)719     public void setFanDirection(final int direction) {
720         mDataStore.setAirflow(SEAT_ALL, direction);
721         setFanDirection(SEAT_ALL, direction);
722     }
723 
setFanDirection(final int zone, final int direction)724     private void setFanDirection(final int zone, final int direction) {
725         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
726             protected Void doInBackground(Void... unused) {
727                 if (mHvacManager != null) {
728                     try {
729                         mHvacManager.setIntProperty(
730                                 CarHvacManager.ID_ZONED_FAN_DIRECTION, zone, direction);
731                     } catch (android.car.CarNotConnectedException e) {
732                         Log.e(TAG, "Car not connected in setAirflowIndex");
733                     }
734                 }
735                 return null;
736             }
737         };
738         task.execute();
739     }
740 
741 
fetchAirCirculation()742     private void fetchAirCirculation() {
743         if (mHvacManager != null) {
744             try {
745                 mDataStore.setAirCirculationState(mHvacManager
746                         .getBooleanProperty(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
747                                 SEAT_ALL));
748             } catch (android.car.CarNotConnectedException e) {
749                 Log.e(TAG, "Car not connected in fetchAirCirculationState");
750             }
751         }
752     }
753 
getAirCirculationState()754     public boolean getAirCirculationState() {
755         return mDataStore.getAirCirculationState();
756     }
757 
setAirCirculation(final boolean state)758     public void setAirCirculation(final boolean state) {
759         mDataStore.setAirCirculationState(state);
760         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
761             protected Void doInBackground(Void... unused) {
762                 if (mHvacManager != null) {
763                     try {
764                         mHvacManager.setBooleanProperty(
765                                 CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
766                                 SEAT_ALL, state);
767                     } catch (android.car.CarNotConnectedException e) {
768                         Log.e(TAG, "Car not connected in setAcState");
769                     }
770                 }
771                 return null;
772             }
773         };
774         task.execute();
775     }
776 
getAutoModeState()777     public boolean getAutoModeState() {
778         return mDataStore.getAutoModeState();
779     }
780 
setAutoMode(final boolean state)781     public void setAutoMode(final boolean state) {
782         mDataStore.setAutoModeState(state);
783         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
784             protected Void doInBackground(Void... unused) {
785                 if (mHvacManager != null) {
786                     try {
787                         mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON,
788                                 SEAT_ALL, state);
789                     } catch (android.car.CarNotConnectedException e) {
790                         Log.e(TAG, "Car not connected in setAutoModeState");
791                     }
792                 }
793                 return null;
794             }
795         };
796         task.execute();
797     }
798 
getHvacPowerState()799     public boolean getHvacPowerState() {
800         return mDataStore.getHvacPowerState();
801     }
802 
fetchHvacPowerState()803     private void fetchHvacPowerState() {
804         if (mHvacManager != null) {
805             try {
806                 mDataStore.setHvacPowerState(mHvacManager.getBooleanProperty(
807                         CarHvacManager.ID_ZONED_HVAC_POWER_ON, SEAT_ALL));
808             } catch (android.car.CarNotConnectedException e) {
809                 Log.e(TAG, "Car not connected in fetchHvacPowerState");
810             }
811         }
812     }
813 }
814