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.VehicleSeat;
20 import android.car.VehicleWindow;
21 import android.car.VehicleZone;
22 import android.car.hardware.CarPropertyConfig;
23 import android.car.hardware.CarPropertyValue;
24 import android.car.hardware.hvac.CarHvacManager;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.os.AsyncTask;
28 import android.os.Binder;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.SystemProperties;
32 import android.support.car.Car;
33 import android.support.car.CarNotConnectedException;
34 import android.support.car.CarConnectionCallback;
35 import android.util.Log;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 import javax.annotation.concurrent.GuardedBy;
41 
42 public 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 
onPassengerTemperatureChange(float temp)60         public void onPassengerTemperatureChange(float temp) {
61         }
62 
onDriverTemperatureChange(float temp)63         public void onDriverTemperatureChange(float temp) {
64         }
65 
onFanSpeedChange(int position)66         public void onFanSpeedChange(int position) {
67         }
68 
onAcStateChange(boolean isOn)69         public void onAcStateChange(boolean isOn) {
70         }
71 
onFrontDefrosterChange(boolean isOn)72         public void onFrontDefrosterChange(boolean isOn) {
73         }
74 
onRearDefrosterChange(boolean isOn)75         public void onRearDefrosterChange(boolean isOn) {
76         }
77 
onPassengerSeatWarmerChange(int level)78         public void onPassengerSeatWarmerChange(int level) {
79         }
80 
onDriverSeatWarmerChange(int level)81         public void onDriverSeatWarmerChange(int level) {
82         }
83 
onFanDirectionChange(int direction)84         public void onFanDirectionChange(int direction) {
85         }
86 
onAirCirculationChange(boolean isOn)87         public void onAirCirculationChange(boolean isOn) {
88         }
89 
onAutoModeChange(boolean isOn)90         public void onAutoModeChange(boolean isOn) {
91         }
92 
onHvacPowerChange(boolean isOn)93         public void onHvacPowerChange(boolean isOn){
94         }
95     }
96 
97     public class LocalBinder extends Binder {
getService()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
onCreate()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
onDestroy()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
onStartCommand(Intent intent, int flags, int startId)141     public int onStartCommand(Intent intent, int flags, int startId) {
142         return START_STICKY;
143     }
144 
145     @Override
onBind(Intent intent)146     public IBinder onBind(Intent intent) {
147         return mBinder;
148     }
149 
registerCallback(Callback callback)150     public void registerCallback(Callback callback) {
151         synchronized (mCallbacks) {
152             mCallbacks.add(callback);
153         }
154     }
155 
unregisterCallback(Callback callback)156     public void unregisterCallback(Callback callback) {
157         synchronized (mCallbacks) {
158             mCallbacks.remove(callback);
159         }
160     }
161 
initHvacManager(CarHvacManager carHvacManager)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")
getValue(CarPropertyValue propertyValue)241     public static <E> E getValue(CarPropertyValue propertyValue) {
242         return (E) propertyValue.getValue();
243     }
244 
handleHvacPowerOn(boolean isOn)245     void handleHvacPowerOn(boolean isOn) {
246         if (mDataStore.shouldPropagateHvacPowerUpdate(isOn)) {
247             synchronized (mCallbacks) {
248                 for (int i = 0; i < mCallbacks.size(); i++) {
249                     mCallbacks.get(i).onHvacPowerChange(isOn);
250                 }
251             }
252         }
253     }
254 
handleSeatWarmerUpdate(int zone, int level)255     void handleSeatWarmerUpdate(int zone, int level) {
256         if (mDataStore.shouldPropagateSeatWarmerLevelUpdate(zone, level)) {
257             synchronized (mCallbacks) {
258                 for (int i = 0; i < mCallbacks.size(); i++) {
259                     if (zone == VehicleZone.ZONE_ROW_1_LEFT) {
260                         mCallbacks.get(i).onDriverSeatWarmerChange(level);
261                     } else {
262                         mCallbacks.get(i).onPassengerSeatWarmerChange(level);
263                     }
264                 }
265             }
266         }
267     }
268 
handleAirCirculationUpdate(boolean airCirculationState)269     private void handleAirCirculationUpdate(boolean airCirculationState) {
270         if (mDataStore.shouldPropagateAirCirculationUpdate(airCirculationState)) {
271             synchronized (mCallbacks) {
272                 for (int i = 0; i < mCallbacks.size(); i++) {
273                     mCallbacks.get(i).onAirCirculationChange(airCirculationState);
274                 }
275             }
276         }
277     }
278 
handleAutoModeUpdate(boolean autoModeState)279     private void handleAutoModeUpdate(boolean autoModeState) {
280         if (mDataStore.shouldPropagateAutoModeUpdate(autoModeState)) {
281             synchronized (mCallbacks) {
282                 for (int i = 0; i < mCallbacks.size(); i++) {
283                     mCallbacks.get(i).onAutoModeChange(autoModeState);
284                 }
285             }
286         }
287     }
288 
handleAcStateUpdate(boolean acState)289     private void handleAcStateUpdate(boolean acState) {
290         if (mDataStore.shouldPropagateAcUpdate(acState)) {
291             synchronized (mCallbacks) {
292                 for (int i = 0; i < mCallbacks.size(); i++) {
293                     mCallbacks.get(i).onAcStateChange(acState);
294                 }
295             }
296         }
297     }
298 
handleFanPositionUpdate(int zone, int position)299     private void handleFanPositionUpdate(int zone, int position) {
300         int index = fanPositionToAirflowIndex(position);
301         if (mDataStore.shouldPropagateFanPositionUpdate(zone, index)) {
302             synchronized (mCallbacks) {
303                 for (int i = 0; i < mCallbacks.size(); i++) {
304                     mCallbacks.get(i).onFanDirectionChange(position);
305                 }
306             }
307         }
308     }
309 
handleFanSpeedUpdate(int zone, int speed)310     private void handleFanSpeedUpdate(int zone, int speed) {
311         if (mDataStore.shouldPropagateFanSpeedUpdate(zone, speed)) {
312             synchronized (mCallbacks) {
313                 for (int i = 0; i < mCallbacks.size(); i++) {
314                     mCallbacks.get(i).onFanSpeedChange(speed);
315                 }
316             }
317         }
318     }
319 
handleTempUpdate(int zone, float temp)320     private void handleTempUpdate(int zone, float temp) {
321         if (mDataStore.shouldPropagateTempUpdate(zone, temp)) {
322             int userTemperature =  mPolicy.hardwareToUserTemp(temp);
323             synchronized (mCallbacks) {
324                 for (int i = 0; i < mCallbacks.size(); i++) {
325                     if (zone == VehicleZone.ZONE_ROW_1_LEFT) {
326                         mCallbacks.get(i)
327                                 .onDriverTemperatureChange(userTemperature);
328                     } else {
329                         mCallbacks.get(i)
330                                 .onPassengerTemperatureChange(userTemperature);
331                     }
332                 }
333             }
334         }
335     }
336 
handleDefrosterUpdate(int zone, boolean defrosterState)337     private void handleDefrosterUpdate(int zone, boolean defrosterState) {
338         if (mDataStore.shouldPropagateDefrosterUpdate(zone, defrosterState)) {
339             synchronized (mCallbacks) {
340                 for (int i = 0; i < mCallbacks.size(); i++) {
341                     if (zone == VehicleWindow.WINDOW_FRONT_WINDSHIELD) {
342                         mCallbacks.get(i).onFrontDefrosterChange(defrosterState);
343                     } else if (zone == VehicleWindow.WINDOW_REAR_WINDSHIELD) {
344                         mCallbacks.get(i).onRearDefrosterChange(defrosterState);
345                     }
346                 }
347             }
348 
349         }
350     }
351 
requestRefresh(final Runnable r, final Handler h)352     public void requestRefresh(final Runnable r, final Handler h) {
353         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
354             @Override
355             protected Void doInBackground(Void... unused) {
356                 synchronized (mHvacManagerReady) {
357                     while (mHvacManager == null) {
358                         try {
359                             mHvacManagerReady.wait();
360                         } catch (InterruptedException e) {
361                             // We got interrupted so we might be shutting down.
362                             return null;
363                         }
364                     }
365                 }
366                 fetchTemperature(DRIVER_ZONE_ID);
367                 fetchTemperature(PASSENGER_ZONE_ID);
368                 fetchFanSpeed();
369                 fetchDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD);
370                 fetchDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD);
371                 fetchAirflow(DRIVER_ZONE_ID);
372                 fetchAirflow(PASSENGER_ZONE_ID);
373                 fetchAcState();
374                 fetchAirCirculation();
375                 fetchHvacPowerState();
376                 return null;
377             }
378 
379             @Override
380             protected void onPostExecute(Void unused) {
381                 h.post(r);
382             }
383         };
384         task.execute();
385     }
386 
getPolicy()387     public HvacPolicy getPolicy() {
388         return mPolicy;
389     }
390 
fetchTemperature(int zone)391     private void fetchTemperature(int zone) {
392         if (mHvacManager != null) {
393             try {
394                 mDataStore.setTemperature(zone, mHvacManager.getFloatProperty(
395                         CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone));
396             } catch (android.car.CarNotConnectedException e) {
397                 Log.e(TAG, "Car not connected in fetchTemperature");
398             }
399         }
400     }
401 
getDriverTemperature()402     public int getDriverTemperature() {
403         return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(DRIVER_ZONE_ID));
404     }
405 
getPassengerTemperature()406     public int getPassengerTemperature() {
407         return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(PASSENGER_ZONE_ID));
408     }
409 
setDriverTemperature(int temperature)410     public void setDriverTemperature(int temperature) {
411         setTemperature(DRIVER_ZONE_ID, mPolicy.userToHardwareTemp(temperature));
412     }
413 
setPassengerTemperature(int temperature)414     public void setPassengerTemperature(int temperature) {
415         setTemperature(PASSENGER_ZONE_ID, mPolicy.userToHardwareTemp(temperature));
416     }
417 
setTemperature(final int zone, final float temperature)418     public void setTemperature(final int zone, final float temperature) {
419         mDataStore.setTemperature(zone, temperature);
420         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
421             protected Void doInBackground(Void... unused) {
422                 if (mHvacManager != null) {
423                     try {
424                         mHvacManager.setFloatProperty(
425                                 CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone, temperature);
426                     } catch (android.car.CarNotConnectedException e) {
427                         Log.e(TAG, "Car not connected in setTemperature");
428                     }
429                 }
430                 return null;
431             }
432         };
433         task.execute();
434     }
435 
setDriverSeatWarmerLevel(int level)436     public void setDriverSeatWarmerLevel(int level) {
437         setSeatWarmerLevel(DRIVER_ZONE_ID, level);
438     }
439 
setPassengerSeatWarmerLevel(int level)440     public void setPassengerSeatWarmerLevel(int level) {
441         setSeatWarmerLevel(PASSENGER_ZONE_ID, level);
442     }
443 
setSeatWarmerLevel(final int zone, final int level)444     public void setSeatWarmerLevel(final int zone, final int level) {
445         mDataStore.setSeatWarmerLevel(zone, level);
446         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
447             protected Void doInBackground(Void... unused) {
448                 if (mHvacManager != null) {
449                     try {
450                         mHvacManager.setIntProperty(
451                                 CarHvacManager.ID_ZONED_SEAT_TEMP, zone, level);
452                     } catch (android.car.CarNotConnectedException e) {
453                         Log.e(TAG, "Car not connected in setSeatWarmerLevel");
454                     }
455                 }
456                 return null;
457             }
458         };
459         task.execute();
460     }
461 
fetchFanSpeed()462     private void fetchFanSpeed() {
463         if (mHvacManager != null) {
464             int zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
465             try {
466                 mDataStore.setFanSpeed(mHvacManager.getIntProperty(
467                         CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone));
468             } catch (android.car.CarNotConnectedException e) {
469                 Log.e(TAG, "Car not connected in fetchFanSpeed");
470             }
471         }
472     }
473 
getFanSpeed()474     public int getFanSpeed() {
475         return mDataStore.getFanSpeed();
476     }
477 
setFanSpeed(final int fanSpeed)478     public void setFanSpeed(final int fanSpeed) {
479         mDataStore.setFanSpeed(fanSpeed);
480 
481         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
482             int newFanSpeed;
483 
484             protected Void doInBackground(Void... unused) {
485                 if (mHvacManager != null) {
486                     int zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
487                     try {
488                         if (Log.isLoggable(TAG, Log.DEBUG)) {
489                             Log.d(TAG, "Setting fanspeed to: " + fanSpeed);
490                         }
491                         mHvacManager.setIntProperty(
492                                 CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed);
493 
494                         newFanSpeed = mHvacManager.getIntProperty(
495                                 CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);
496                     } catch (android.car.CarNotConnectedException e) {
497                         Log.e(TAG, "Car not connected in setFanSpeed");
498                     }
499                 }
500                 return null;
501             }
502 
503             @Override
504             protected void onPostExecute(final Void result) {
505                 Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
506             }
507         };
508         task.execute();
509     }
510 
fetchDefrosterState(int zone)511     private void fetchDefrosterState(int zone) {
512         if (mHvacManager != null) {
513             try {
514                 mDataStore.setDefrosterState(zone, mHvacManager.getBooleanProperty(
515                         CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone));
516             } catch (android.car.CarNotConnectedException e) {
517                 Log.e(TAG, "Car not connected in fetchDefrosterState");
518             }
519         }
520     }
521 
getFrontDefrosterState()522     public boolean getFrontDefrosterState() {
523         return mDataStore.getDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD);
524     }
525 
getRearDefrosterState()526     public boolean getRearDefrosterState() {
527         return mDataStore.getDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD);
528     }
529 
setFrontDefrosterState(boolean state)530     public void setFrontDefrosterState(boolean state) {
531         setDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD, state);
532     }
533 
setRearDefrosterState(boolean state)534     public void setRearDefrosterState(boolean state) {
535         setDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD, state);
536     }
537 
setDefrosterState(final int zone, final boolean state)538     public void setDefrosterState(final int zone, final boolean state) {
539         mDataStore.setDefrosterState(zone, state);
540         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
541             protected Void doInBackground(Void... unused) {
542                 if (mHvacManager != null) {
543                     try {
544                         mHvacManager.setBooleanProperty(
545                                 CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone, state);
546                     } catch (android.car.CarNotConnectedException e) {
547                         Log.e(TAG, "Car not connected in setDeforsterState");
548                     }
549                 }
550                 return null;
551             }
552         };
553         task.execute();
554     }
555 
fetchAcState()556     private void fetchAcState() {
557         if (mHvacManager != null) {
558             try {
559                 mDataStore.setAcState(mHvacManager.getBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
560                         VehicleZone.ZONE_ROW_1_ALL));
561             } catch (android.car.CarNotConnectedException e) {
562                 Log.e(TAG, "Car not connected in fetchAcState");
563             }
564         }
565     }
566 
getAcState()567     public boolean getAcState() {
568         return mDataStore.getAcState();
569     }
570 
setAcState(final boolean state)571     public void setAcState(final boolean state) {
572         mDataStore.setAcState(state);
573         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
574             protected Void doInBackground(Void... unused) {
575                 if (mHvacManager != null) {
576                     try {
577                         mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
578                                 VehicleZone.ZONE_ROW_1_ALL, state);
579                     } catch (android.car.CarNotConnectedException e) {
580                         Log.e(TAG, "Car not connected in setAcState");
581                     }
582                 }
583                 return null;
584             }
585         };
586         task.execute();
587     }
588 
fanPositionToAirflowIndex(int fanPosition)589     private int fanPositionToAirflowIndex(int fanPosition) {
590         for (int i = 0; i < AIRFLOW_STATES.length; i++) {
591             if (fanPosition == AIRFLOW_STATES[i]) {
592                 return i;
593             }
594         }
595         Log.e(TAG, "Unknown fan position " + fanPosition + ". Returning default.");
596         return AIRFLOW_STATES[0];
597     }
598 
fetchAirflow(int zone)599     private void fetchAirflow(int zone) {
600         if (mHvacManager != null) {
601             zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
602             try {
603                 int val = mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_POSITION, zone);
604                 mDataStore.setAirflow(zone, fanPositionToAirflowIndex(val));
605             } catch (android.car.CarNotConnectedException e) {
606                 Log.e(TAG, "Car not connected in fetchAirFlow");
607             }
608         }
609     }
610 
getAirflowIndex(int zone)611     public int getAirflowIndex(int zone) {
612         return mDataStore.getAirflow(zone);
613     }
614 
setAirflowIndex(final int zone, final int index)615     public void setAirflowIndex(final int zone, final int index) {
616         mDataStore.setAirflow(zone, index);
617         int override = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround.
618         int val = AIRFLOW_STATES[index];
619         setFanDirection(override, val);
620     }
621 
setFanDirection(final int direction)622     public void setFanDirection(final int direction) {
623         mDataStore.setAirflow(VehicleZone.ZONE_ROW_1_ALL, direction);
624         setFanDirection(VehicleZone.ZONE_ROW_1_ALL, direction);
625     }
626 
setFanDirection(final int zone, final int direction)627     private void setFanDirection(final int zone, final int direction) {
628         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
629             protected Void doInBackground(Void... unused) {
630                 if (mHvacManager != null) {
631                     try {
632                         mHvacManager.setIntProperty(
633                                 CarHvacManager.ID_ZONED_FAN_POSITION, zone, direction);
634                     } catch (android.car.CarNotConnectedException e) {
635                         Log.e(TAG, "Car not connected in setAirflowIndex");
636                     }
637                 }
638                 return null;
639             }
640         };
641         task.execute();
642     }
643 
644 
fetchAirCirculation()645     private void fetchAirCirculation() {
646         if (mHvacManager != null) {
647             try {
648                 mDataStore.setAirCirculationState(mHvacManager
649                         .getBooleanProperty(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
650                                 VehicleZone.ZONE_ROW_1_ALL));
651             } catch (android.car.CarNotConnectedException e) {
652                 Log.e(TAG, "Car not connected in fetchAirCirculationState");
653             }
654         }
655     }
656 
getAirCirculationState()657     public boolean getAirCirculationState() {
658         return mDataStore.getAirCirculationState();
659     }
660 
setAirCirculation(final boolean state)661     public void setAirCirculation(final boolean state) {
662         mDataStore.setAirCirculationState(state);
663         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
664             protected Void doInBackground(Void... unused) {
665                 if (mHvacManager != null) {
666                     try {
667                         mHvacManager.setBooleanProperty(
668                                 CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
669                                 VehicleZone.ZONE_ROW_1_ALL, state);
670                     } catch (android.car.CarNotConnectedException e) {
671                         Log.e(TAG, "Car not connected in setAcState");
672                     }
673                 }
674                 return null;
675             }
676         };
677         task.execute();
678     }
679 
getAutoModeState()680     public boolean getAutoModeState() {
681         return mDataStore.getAutoModeState();
682     }
683 
setAutoMode(final boolean state)684     public void setAutoMode(final boolean state) {
685         mDataStore.setAutoModeState(state);
686         final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
687             protected Void doInBackground(Void... unused) {
688                 if (mHvacManager != null) {
689                     try {
690                         mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON,
691                                 VehicleZone.ZONE_ROW_1_ALL, state);
692                     } catch (android.car.CarNotConnectedException e) {
693                         Log.e(TAG, "Car not connected in setAutoModeState");
694                     }
695                 }
696                 return null;
697             }
698         };
699         task.execute();
700     }
701 
getHvacPowerState()702     public boolean getHvacPowerState() {
703         return mDataStore.getHvacPowerState();
704     }
705 
fetchHvacPowerState()706     private void fetchHvacPowerState() {
707         if (mHvacManager != null) {
708             try {
709                 mDataStore.setHvacPowerState(mHvacManager.getBooleanProperty(
710                         CarHvacManager.ID_ZONED_HVAC_POWER_ON, VehicleZone.ZONE_ROW_1_ALL));
711             } catch (android.car.CarNotConnectedException e) {
712                 Log.e(TAG, "Car not connected in fetchHvacPowerState");
713             }
714         }
715     }
716 }
717