1 /*
2  * Copyright (C) 2019 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.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 import static com.google.common.truth.Truth.assertWithMessage;
23 
24 import static org.junit.Assert.assertThrows;
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.ArgumentMatchers.anyFloat;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.Mockito.atLeast;
30 import static org.mockito.Mockito.doNothing;
31 import static org.mockito.Mockito.doThrow;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.times;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import static java.lang.Integer.toHexString;
38 
39 import android.car.hardware.property.ICarPropertyEventListener;
40 import android.hardware.automotive.vehicle.VehicleAreaWheel;
41 import android.hardware.automotive.vehicle.VehicleGear;
42 import android.hardware.automotive.vehicle.VehiclePropValue;
43 import android.hardware.automotive.vehicle.VehicleProperty;
44 import android.hardware.automotive.vehicle.VehiclePropertyAccess;
45 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode;
46 import android.os.IBinder;
47 import android.os.ServiceSpecificException;
48 import android.os.SystemClock;
49 import android.util.Log;
50 
51 import androidx.test.ext.junit.runners.AndroidJUnit4;
52 import androidx.test.filters.MediumTest;
53 
54 import com.android.car.hal.test.AidlMockedVehicleHal.VehicleHalPropertyHandler;
55 import com.android.car.hal.test.AidlVehiclePropValueBuilder;
56 import com.android.car.internal.property.CarSubscription;
57 
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.mockito.ArgumentCaptor;
61 import org.mockito.Mock;
62 
63 import java.util.HashMap;
64 import java.util.HashSet;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Set;
68 
69 /**
70  * Test for {@link com.android.car.CarPropertyService}
71  */
72 @RunWith(AndroidJUnit4.class)
73 @MediumTest
74 public class CarPropertyServiceTest extends MockedCarTestBase {
75     private static final String TAG = CarPropertyServiceTest.class.getSimpleName();
76 
77     private final Map<Integer, VehiclePropValue> mDefaultPropValues = new HashMap<>();
78 
79     private CarPropertyService mService;
80 
81     @Mock
82     private VehicleHalPropertyHandler mMockPropertyHandler;
83 
84     // This is a zoned continuous property with two areas used by subscription testing.
85     private static final int TEST_SUBSCRIBE_PROP = VehicleProperty.TIRE_PRESSURE;
86 
CarPropertyServiceTest()87     public CarPropertyServiceTest() {
88         // Unusual default values for the vehicle properties registered to listen via
89         // CarPropertyService.registerListener. Unusual default values like the car is in motion,
90         // night mode is on, or the car is low on fuel.
91         mDefaultPropValues.put(VehicleProperty.GEAR_SELECTION,
92                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
93                 .addIntValues(VehicleGear.GEAR_DRIVE)
94                 .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
95         mDefaultPropValues.put(VehicleProperty.PARKING_BRAKE_ON,
96                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
97                 .setBooleanValue(false)
98                 .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
99         mDefaultPropValues.put(VehicleProperty.PERF_VEHICLE_SPEED,
100                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
101                 .addFloatValues(30.0f)
102                 .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
103         mDefaultPropValues.put(VehicleProperty.NIGHT_MODE,
104                 AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
105                 .setBooleanValue(true)
106                 .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
107     }
108 
109     @Override
configureMockedHal()110     protected void configureMockedHal() {
111         PropertyHandler handler = new PropertyHandler();
112         for (VehiclePropValue value : mDefaultPropValues.values()) {
113             handler.onPropertySet(value);
114             addAidlProperty(value.prop, handler);
115         }
116         addAidlProperty(TEST_SUBSCRIBE_PROP, mMockPropertyHandler)
117                 .setChangeMode(VehiclePropertyChangeMode.CONTINUOUS)
118                 .setAccess(VehiclePropertyAccess.READ)
119                 .addAreaConfig(VehicleAreaWheel.LEFT_FRONT)
120                 .addAreaConfig(VehicleAreaWheel.RIGHT_FRONT)
121                 .setMinSampleRate(0f)
122                 .setMaxSampleRate(100f);
123     }
124 
125     @Override
spyOnBeforeCarImplInit(ICarImpl carImpl)126     protected void spyOnBeforeCarImplInit(ICarImpl carImpl) {
127         mService = CarLocalServices.getService(CarPropertyService.class);
128         assertThat(mService).isNotNull();
129         spyOn(mService);
130     }
131 
132     @Test
testMatchesDefaultPropertyValues()133     public void testMatchesDefaultPropertyValues() {
134         Set<Integer> expectedPropIds = mDefaultPropValues.keySet();
135         ArgumentCaptor<Integer> propIdCaptor = ArgumentCaptor.forClass(Integer.class);
136         verify(mService, atLeast(expectedPropIds.size())).registerListener(
137                 propIdCaptor.capture(), anyFloat(), any());
138 
139         Set<Integer> actualPropIds = new HashSet<Integer>(propIdCaptor.getAllValues());
140         assertWithMessage("Should assign default values for missing property IDs")
141                 .that(expectedPropIds).containsAtLeastElementsIn(actualPropIds.toArray());
142         assertWithMessage("Missing registerListener for property IDs")
143                 .that(actualPropIds).containsAtLeastElementsIn(expectedPropIds.toArray());
144     }
145 
146     @Test
testregisterListener()147     public void testregisterListener() {
148         CarSubscription options = new CarSubscription();
149         options.propertyId = TEST_SUBSCRIBE_PROP;
150         options.areaIds = new int[]{
151                 android.car.VehicleAreaWheel.WHEEL_LEFT_FRONT,
152                 android.car.VehicleAreaWheel.WHEEL_RIGHT_FRONT
153         };
154         options.updateRateHz = 10f;
155         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
156         IBinder mockBinder = mock(IBinder.class);
157         when(mockHandler.asBinder()).thenReturn(mockBinder);
158         // This is for initial get value requests.
159         when(mMockPropertyHandler.onPropertyGet(any())).thenReturn(
160                 AidlVehiclePropValueBuilder.newBuilder(TEST_SUBSCRIBE_PROP)
161                     .addFloatValues(1.23f)
162                     .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
163 
164         mService.registerListener(List.of(options), mockHandler);
165 
166         ArgumentCaptor<int[]> areaIdsCaptor = ArgumentCaptor.forClass(int[].class);
167 
168         verify(mMockPropertyHandler).onPropertySubscribe(eq(TEST_SUBSCRIBE_PROP),
169                 areaIdsCaptor.capture(), eq(10f));
170         assertWithMessage("Received expected areaIds for subscription")
171                 .that(areaIdsCaptor.getValue()).asList().containsExactly(
172                         VehicleAreaWheel.LEFT_FRONT, VehicleAreaWheel.RIGHT_FRONT);
173     }
174 
175     @Test
testregisterListener_exceptionAndRetry()176     public void testregisterListener_exceptionAndRetry() {
177         CarSubscription options = new CarSubscription();
178         options.propertyId = TEST_SUBSCRIBE_PROP;
179         options.areaIds = new int[]{
180                 android.car.VehicleAreaWheel.WHEEL_LEFT_FRONT,
181                 android.car.VehicleAreaWheel.WHEEL_RIGHT_FRONT
182         };
183         options.updateRateHz = 10f;
184         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
185         IBinder mockBinder = mock(IBinder.class);
186         when(mockHandler.asBinder()).thenReturn(mockBinder);
187         // This is for initial get value requests.
188         when(mMockPropertyHandler.onPropertyGet(any())).thenReturn(
189                 AidlVehiclePropValueBuilder.newBuilder(TEST_SUBSCRIBE_PROP)
190                     .addFloatValues(1.23f)
191                     .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
192         doThrow(new ServiceSpecificException(0)).when(mMockPropertyHandler).onPropertySubscribe(
193                 anyInt(), any(), anyFloat());
194 
195         assertThrows(ServiceSpecificException.class, () ->
196                 mService.registerListener(List.of(options), mockHandler));
197 
198         // Simulate the error goes away.
199         doNothing().when(mMockPropertyHandler).onPropertySubscribe(anyInt(), any(), anyFloat());
200 
201         ArgumentCaptor<int[]> areaIdsCaptor = ArgumentCaptor.forClass(int[].class);
202 
203         // Retry.
204         mService.registerListener(List.of(options), mockHandler);
205 
206         // The retry must reach VHAL.
207         verify(mMockPropertyHandler, times(2)).onPropertySubscribe(eq(TEST_SUBSCRIBE_PROP),
208                 areaIdsCaptor.capture(), eq(10f));
209         assertWithMessage("Received expected areaIds for subscription")
210                 .that(areaIdsCaptor.getValue()).asList().containsExactly(
211                         VehicleAreaWheel.LEFT_FRONT, VehicleAreaWheel.RIGHT_FRONT);
212     }
213 
214     @Test
testUnregisterListener()215     public void testUnregisterListener() {
216         CarSubscription options = new CarSubscription();
217         options.propertyId = TEST_SUBSCRIBE_PROP;
218         options.areaIds = new int[]{
219                 android.car.VehicleAreaWheel.WHEEL_LEFT_FRONT,
220                 android.car.VehicleAreaWheel.WHEEL_RIGHT_FRONT
221         };
222         options.updateRateHz = 10f;
223         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
224         IBinder mockBinder = mock(IBinder.class);
225         when(mockHandler.asBinder()).thenReturn(mockBinder);
226         // This is for initial get value requests.
227         when(mMockPropertyHandler.onPropertyGet(any())).thenReturn(
228                 AidlVehiclePropValueBuilder.newBuilder(TEST_SUBSCRIBE_PROP)
229                     .addFloatValues(1.23f)
230                     .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
231 
232         mService.registerListener(List.of(options), mockHandler);
233 
234         verify(mMockPropertyHandler).onPropertySubscribe(eq(TEST_SUBSCRIBE_PROP), any(), eq(10f));
235 
236         mService.unregisterListener(TEST_SUBSCRIBE_PROP, mockHandler);
237 
238         verify(mMockPropertyHandler).onPropertyUnsubscribe(TEST_SUBSCRIBE_PROP);
239     }
240 
241     @Test
testUnregisterListener_exceptionAndRetry()242     public void testUnregisterListener_exceptionAndRetry() {
243         CarSubscription options = new CarSubscription();
244         options.propertyId = TEST_SUBSCRIBE_PROP;
245         options.areaIds = new int[]{
246                 android.car.VehicleAreaWheel.WHEEL_LEFT_FRONT,
247                 android.car.VehicleAreaWheel.WHEEL_RIGHT_FRONT
248         };
249         options.updateRateHz = 10f;
250         ICarPropertyEventListener mockHandler = mock(ICarPropertyEventListener.class);
251         IBinder mockBinder = mock(IBinder.class);
252         when(mockHandler.asBinder()).thenReturn(mockBinder);
253         // This is for initial get value requests.
254         when(mMockPropertyHandler.onPropertyGet(any())).thenReturn(
255                 AidlVehiclePropValueBuilder.newBuilder(TEST_SUBSCRIBE_PROP)
256                     .addFloatValues(1.23f)
257                     .setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
258         // The first unsubscribe will throw exception, then the error goes away.
259         doThrow(new ServiceSpecificException(0)).doNothing()
260                 .when(mMockPropertyHandler).onPropertyUnsubscribe(anyInt());
261 
262         mService.registerListener(List.of(options), mockHandler);
263 
264         verify(mMockPropertyHandler).onPropertySubscribe(eq(TEST_SUBSCRIBE_PROP), any(), eq(10f));
265 
266         assertThrows(ServiceSpecificException.class, () ->
267                 mService.unregisterListener(TEST_SUBSCRIBE_PROP, mockHandler));
268 
269         // Retry.
270         mService.unregisterListener(TEST_SUBSCRIBE_PROP, mockHandler);
271 
272         // The retry must reach VHAL.
273         verify(mMockPropertyHandler, times(2)).onPropertyUnsubscribe(TEST_SUBSCRIBE_PROP);
274     }
275 
276     private static final class PropertyHandler implements VehicleHalPropertyHandler {
277         private final Map<Integer, VehiclePropValue> mMap = new HashMap<>();
278 
279         @Override
onPropertySet(VehiclePropValue value)280         public synchronized void onPropertySet(VehiclePropValue value) {
281             mMap.put(value.prop, value);
282         }
283 
284         @Override
onPropertyGet(VehiclePropValue value)285         public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
286             assertWithMessage("onPropertyGet missing property: %s", toHexString(value.prop))
287                     .that(mMap).containsKey(value.prop);
288             VehiclePropValue currentValue = mMap.get(value.prop);
289             return currentValue != null ? currentValue : value;
290         }
291 
292         @Override
onPropertySubscribe(int property, float sampleRate)293         public synchronized void onPropertySubscribe(int property, float sampleRate) {
294             assertWithMessage("onPropertySubscribe missing property: %s", toHexString(property))
295                     .that(mMap).containsKey(property);
296             Log.d(TAG, "onPropertySubscribe property "
297                     + property + " sampleRate " + sampleRate);
298         }
299 
300         @Override
onPropertyUnsubscribe(int property)301         public synchronized void onPropertyUnsubscribe(int property) {
302             assertWithMessage("onPropertyUnsubscribe missing property: %s", toHexString(property))
303                     .that(mMap).containsKey(property);
304             Log.d(TAG, "onPropertyUnSubscribe property " + property);
305         }
306     }
307 }
308