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