1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertThrows;
24 import static org.junit.Assert.assertTrue;
25 
26 import android.car.Car;
27 import android.car.hardware.CarPropertyValue;
28 import android.car.hardware.hvac.CarHvacManager;
29 import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback;
30 import android.car.hardware.hvac.CarHvacManager.PropertyId;
31 import android.hardware.automotive.vehicle.VehicleAreaSeat;
32 import android.hardware.automotive.vehicle.VehicleAreaWindow;
33 import android.hardware.automotive.vehicle.VehiclePropValue;
34 import android.hardware.automotive.vehicle.VehicleProperty;
35 import android.hardware.automotive.vehicle.VehiclePropertyAccess;
36 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode;
37 import android.os.SystemClock;
38 import android.util.Log;
39 import android.util.MutableInt;
40 
41 import androidx.test.ext.junit.runners.AndroidJUnit4;
42 import androidx.test.filters.MediumTest;
43 
44 import com.android.car.hal.test.AidlMockedVehicleHal.VehicleHalPropertyHandler;
45 import com.android.car.hal.test.AidlVehiclePropValueBuilder;
46 
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.util.HashMap;
51 import java.util.concurrent.CountDownLatch;
52 import java.util.concurrent.Semaphore;
53 import java.util.concurrent.TimeUnit;
54 
55 @RunWith(AndroidJUnit4.class)
56 @MediumTest
57 public class CarHvacManagerTest extends MockedCarTestBase {
58     private static final String TAG = CarHvacManagerTest.class.getSimpleName();
59 
60     // Use this semaphore to block until the callback is heard of.
61     private Semaphore mAvailable;
62 
63     private CarHvacManager mCarHvacManager;
64     private boolean mEventBoolVal;
65     private float mEventFloatVal;
66     private int mEventIntVal;
67     private int mEventZoneVal;
68     private final HvacPropertyHandler mHandler = new HvacPropertyHandler();
69 
70     @Override
configureMockedHal()71     protected void configureMockedHal() {
72         addAidlProperty(VehicleProperty.HVAC_DEFROSTER, mHandler)
73                 .addAreaConfig(VehicleAreaWindow.FRONT_WINDSHIELD, 0, 0);
74         addAidlProperty(VehicleProperty.HVAC_FAN_SPEED, mHandler)
75                 .addAreaConfig(VehicleAreaSeat.ROW_1_LEFT, 0, 0);
76         addAidlProperty(VehicleProperty.HVAC_TEMPERATURE_SET, mHandler)
77                 .addAreaConfig(VehicleAreaSeat.ROW_1_LEFT, 0, 0);
78         addAidlProperty(VehicleProperty.HVAC_TEMPERATURE_CURRENT, mHandler)
79                 .setChangeMode(VehiclePropertyChangeMode.CONTINUOUS)
80                 .setAccess(VehiclePropertyAccess.READ)
81                 .addAreaConfig(VehicleAreaSeat.ROW_1_LEFT | VehicleAreaSeat.ROW_1_RIGHT, 0, 0);
82         addAidlProperty(VehicleProperty.HVAC_AC_ON, mHandler)
83                 .addAreaConfig(VehicleAreaSeat.ROW_1_CENTER);
84     }
85 
86     @Override
setUp()87     public void setUp() throws Exception {
88         super.setUp();
89         mAvailable = new Semaphore(0);
90         mCarHvacManager = (CarHvacManager) getCar().getCarManager(Car.HVAC_SERVICE);
91         mCarHvacManager.setIntProperty(VehicleProperty.HVAC_FAN_SPEED,
92                 VehicleAreaSeat.ROW_1_LEFT, 0);
93     }
94 
95     // Test a boolean property
96     @Test
testHvacRearDefrosterOn()97     public void testHvacRearDefrosterOn() throws Exception {
98         mCarHvacManager.setBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
99                 VehicleAreaWindow.FRONT_WINDSHIELD, true);
100         boolean defrost = mCarHvacManager.getBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
101                 VehicleAreaWindow.FRONT_WINDSHIELD);
102         assertTrue(defrost);
103 
104         mCarHvacManager.setBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
105                 VehicleAreaWindow.FRONT_WINDSHIELD, false);
106         defrost = mCarHvacManager.getBooleanProperty(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
107                 VehicleAreaWindow.FRONT_WINDSHIELD);
108         assertFalse(defrost);
109     }
110 
111     /**
112      * Test {@link CarHvacManager#isPropertyAvailable(int, int)}
113      */
114     @Test
testHvacPropertyAvailable()115     public void testHvacPropertyAvailable() {
116         assertThat(mCarHvacManager.isPropertyAvailable(VehicleProperty.HVAC_AC_ON,
117                 VehicleAreaSeat.ROW_1_CENTER)).isFalse();
118         assertThat(mCarHvacManager.isPropertyAvailable(VehicleProperty.HVAC_FAN_SPEED,
119                 VehicleAreaSeat.ROW_1_LEFT)).isTrue();
120     }
121 
122     // Test an integer property
123     @Test
testHvacFanSpeed()124     public void testHvacFanSpeed() throws Exception {
125         mCarHvacManager.setIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
126                 VehicleAreaSeat.ROW_1_LEFT, 15);
127         int speed = mCarHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
128                 VehicleAreaSeat.ROW_1_LEFT);
129         assertEquals(15, speed);
130 
131         mCarHvacManager.setIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
132                 VehicleAreaSeat.ROW_1_LEFT, 23);
133         speed = mCarHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
134                 VehicleAreaSeat.ROW_1_LEFT);
135         assertEquals(23, speed);
136     }
137 
138     // Test an float property
139     @Test
testHvacTempSetpoint()140     public void testHvacTempSetpoint() throws Exception {
141         mCarHvacManager.setFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
142                 VehicleAreaSeat.ROW_1_LEFT, 70);
143         float temp = mCarHvacManager.getFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
144                 VehicleAreaSeat.ROW_1_LEFT);
145         assertEquals(70.0, temp, 0);
146 
147         mCarHvacManager.setFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
148                 VehicleAreaSeat.ROW_1_LEFT, (float) 65.5);
149         temp = mCarHvacManager.getFloatProperty(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
150                 VehicleAreaSeat.ROW_1_LEFT);
151         assertEquals(65.5, temp, 0);
152     }
153 
154     @Test
testError()155     public void testError() throws Exception {
156         final int PROP = VehicleProperty.HVAC_DEFROSTER;
157         final int AREA = VehicleAreaWindow.FRONT_WINDSHIELD;
158         final int ERR_CODE = 42;
159 
160         CountDownLatch errorLatch = new CountDownLatch(1);
161         MutableInt propertyIdReceived = new MutableInt(0);
162         MutableInt areaIdReceived = new MutableInt(0);
163 
164         mCarHvacManager.registerCallback(new CarHvacEventCallback()  {
165             @Override
166             public void onChangeEvent(CarPropertyValue value) {
167 
168             }
169 
170             @Override
171             public void onErrorEvent(@PropertyId int propertyId, int area) {
172                 propertyIdReceived.value = propertyId;
173                 areaIdReceived.value = area;
174                 errorLatch.countDown();
175             }
176         });
177         mCarHvacManager.setBooleanProperty(PROP, AREA, true);
178         getAidlMockedVehicleHal().injectError(ERR_CODE, PROP, AREA);
179         assertTrue(errorLatch.await(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
180         assertEquals(PROP, propertyIdReceived.value);
181         assertEquals(AREA, areaIdReceived.value);
182     }
183 
184     // Test an event
185     @Test
testEvent()186     public void testEvent() throws Exception {
187         mCarHvacManager.registerCallback(new EventListener());
188         // Wait for events generated on registration
189         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
190         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
191         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
192         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
193         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
194 
195         // Inject a boolean event and wait for its callback in onPropertySet.
196         VehiclePropValue v = AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_DEFROSTER)
197                 .setAreaId(VehicleAreaWindow.FRONT_WINDSHIELD)
198                 .setTimestamp(SystemClock.elapsedRealtimeNanos())
199                 .addIntValues(1)
200                 .build();
201         assertEquals(0, mAvailable.availablePermits());
202         getAidlMockedVehicleHal().injectEvent(v);
203 
204         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
205         assertTrue(mEventBoolVal);
206         assertEquals(mEventZoneVal, VehicleAreaWindow.FRONT_WINDSHIELD);
207 
208         // Inject a float event and wait for its callback in onPropertySet.
209         v = AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_TEMPERATURE_CURRENT)
210                 .setAreaId(VehicleAreaSeat.ROW_1_LEFT | VehicleAreaSeat.ROW_1_RIGHT)
211                 .setTimestamp(SystemClock.elapsedRealtimeNanos())
212                 .addFloatValues(67f)
213                 .build();
214         assertEquals(0, mAvailable.availablePermits());
215         getAidlMockedVehicleHal().injectEvent(v);
216 
217         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
218         assertEquals(67, mEventFloatVal, 0);
219         assertEquals(VehicleAreaSeat.ROW_1_LEFT | VehicleAreaSeat.ROW_1_RIGHT, mEventZoneVal);
220 
221         // Inject an integer event and wait for its callback in onPropertySet.
222         v = AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_FAN_SPEED)
223                 .setAreaId(VehicleAreaSeat.ROW_1_LEFT)
224                 .setTimestamp(SystemClock.elapsedRealtimeNanos())
225                 .addIntValues(4)
226                 .build();
227         assertEquals(0, mAvailable.availablePermits());
228         getAidlMockedVehicleHal().injectEvent(v);
229 
230         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
231         assertEquals(4, mEventIntVal);
232         assertEquals(VehicleAreaSeat.ROW_1_LEFT, mEventZoneVal);
233     }
234 
235     /**
236      * Test {@link CarHvacManager#unregisterCallback(CarHvacEventCallback)}
237      */
238     @Test
testUnregisterCallback()239     public void testUnregisterCallback() throws Exception {
240         EventListener listener = new EventListener();
241         mCarHvacManager.registerCallback(listener);
242         // Wait for events generated on registration
243         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
244         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
245         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
246         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
247         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
248 
249         // Inject a boolean event and wait for its callback in onPropertySet.
250         VehiclePropValue v = AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_DEFROSTER)
251                 .setAreaId(VehicleAreaWindow.FRONT_WINDSHIELD)
252                 .setTimestamp(SystemClock.elapsedRealtimeNanos())
253                 .addIntValues(1)
254                 .build();
255         assertEquals(0, mAvailable.availablePermits());
256         getAidlMockedVehicleHal().injectEvent(v);
257 
258         // Verify client get the callback.
259         assertTrue(mAvailable.tryAcquire(2L, TimeUnit.SECONDS));
260         assertTrue(mEventBoolVal);
261         assertEquals(mEventZoneVal, VehicleAreaWindow.FRONT_WINDSHIELD);
262 
263         // test unregister callback
264         mCarHvacManager.unregisterCallback(listener);
265         assertThrows(AssertionError.class, () -> getAidlMockedVehicleHal().injectEvent(v));
266     }
267 
268     private static final class HvacPropertyHandler implements VehicleHalPropertyHandler {
269         HashMap<Integer, VehiclePropValue> mMap = new HashMap<>();
270 
271         @Override
onPropertySet(VehiclePropValue value)272         public synchronized void onPropertySet(VehiclePropValue value) {
273             mMap.put(value.prop, value);
274         }
275 
276         @Override
onPropertyGet(VehiclePropValue value)277         public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
278             VehiclePropValue currentValue = mMap.get(value.prop);
279             // VNS will call get method when subscribe is called, just return empty value.
280             return currentValue != null ? currentValue : value;
281         }
282 
283         @Override
onPropertySubscribe(int property, float sampleRate)284         public synchronized void onPropertySubscribe(int property, float sampleRate) {
285             Log.d(TAG, "onPropertySubscribe property " + property + " sampleRate " + sampleRate);
286             if (mMap.get(property) == null) {
287                 Log.d(TAG, "onPropertySubscribe add placeholder property: " + property);
288                 int areaId = 0;
289                 switch (property) {
290                     case VehicleProperty.HVAC_DEFROSTER:
291                         areaId = VehicleAreaWindow.FRONT_WINDSHIELD;
292                         break;
293                     case VehicleProperty.HVAC_FAN_SPEED:
294                         // Fall through
295                     case VehicleProperty.HVAC_TEMPERATURE_SET:
296                         areaId = VehicleAreaSeat.ROW_1_LEFT;
297                         break;
298                     case VehicleProperty.HVAC_AC_ON:
299                         areaId = VehicleAreaSeat.ROW_1_CENTER;
300                         break;
301                     case VehicleProperty.HVAC_TEMPERATURE_CURRENT:
302                         areaId = VehicleAreaSeat.ROW_1_LEFT | VehicleAreaSeat.ROW_1_RIGHT;
303                         break;
304                 }
305                 VehiclePropValue placeholderValue = AidlVehiclePropValueBuilder.newBuilder(property)
306                         .setAreaId(areaId)
307                         .setTimestamp(SystemClock.elapsedRealtimeNanos())
308                         .addIntValues(1)
309                         .addFloatValues(1)
310                         .build();
311                 mMap.put(property, placeholderValue);
312             }
313         }
314 
315         @Override
onPropertyUnsubscribe(int property)316         public synchronized void onPropertyUnsubscribe(int property) {
317             Log.d(TAG, "onPropertyUnSubscribe property " + property);
318         }
319     }
320 
321     private class EventListener implements CarHvacEventCallback {
EventListener()322         EventListener() { }
323 
324         @Override
onChangeEvent(final CarPropertyValue value)325         public void onChangeEvent(final CarPropertyValue value) {
326             Log.d(TAG, "onChangeEvent: "  + value);
327             Object o = value.getValue();
328             mEventZoneVal = value.getAreaId();
329 
330             if (o instanceof Integer) {
331                 mEventIntVal = (Integer) o;
332             } else if (o instanceof Float) {
333                 mEventFloatVal = (Float) o;
334             } else if (o instanceof Boolean) {
335                 mEventBoolVal = (Boolean) o;
336             }
337             mAvailable.release();
338         }
339 
340         @Override
onErrorEvent(final int propertyId, final int zone)341         public void onErrorEvent(final int propertyId, final int zone) {
342             Log.d(TAG, "Error:  propertyId=" + propertyId + "  zone=" + zone);
343         }
344     }
345 }
346