blob: 8e41c19f6022c154216922f23f13624fb89ddc5b [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.vehiclehal.test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static java.lang.Integer.toHexString;
import android.car.Car;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
import android.car.hardware.property.VehicleVendorPermission;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The test suite will execute end-to-end Car Property API test by generating VHAL property data
* from default VHAL and verify those data on the fly. The test data is coming from assets/ folder
* in the test APK and will be shared with VHAL to execute the test.
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class CarPropertyTest extends E2eCarTestBase {
private static final String TAG = Utils.concatTag(CarPropertyTest.class);
// Test should be completed within 10 minutes as it only covers a finite set of properties
private static final Duration TEST_TIME_OUT = Duration.ofMinutes(3);
private static final String CAR_HVAC_TEST_JSON = "car_hvac_test.json";
private static final String CAR_HVAC_TEST_SET_JSON = "car_hvac_test.json";
private static final String CAR_INFO_TEST_JSON = "car_info_test.json";
// kMixedTypePropertyForTest property ID
private static final int MIXED_TYPE_PROPERTY = 0x21e01111;
// kSetPropertyFromVehicleForTest
private static final int SET_INT_FROM_VEHICLE = 0x21e01112;
private static final int SET_FLOAT_FROM_VEHICLE = 0x21e01113;
private static final int SET_BOOLEAN_FROM_VEHICLE = 0x21e01114;
private static final int WAIT_FOR_CALLBACK = 200;
// kMixedTypePropertyForTest default value
private static final Object[] DEFAULT_VALUE = {"MIXED property", true, 2, 3, 4.5f};
private static final String CAR_PROPERTY_TEST_JSON = "car_property_test.json";
private static final int GEAR_PROPERTY_ID = 289408000;
private static final Set<String> VENDOR_PERMISSIONS = new HashSet<>(Arrays.asList(
Car.PERMISSION_VENDOR_EXTENSION,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_WINDOW,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_WINDOW,
// permissions for the property related with door
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_DOOR,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_DOOR,
// permissions for the property related with seat
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_SEAT,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_SEAT,
// permissions for the property related with mirror
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_MIRROR,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_MIRROR,
// permissions for the property related with car's information
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_INFO,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_INFO,
// permissions for the property related with car's engine
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_ENGINE,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_ENGINE,
// permissions for the property related with car's HVAC
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_HVAC,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_HVAC,
// permissions for the property related with car's light
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_LIGHT,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_LIGHT,
// permissions reserved for other vendor permission
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_1,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_1,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_2,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_2,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_3,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_3,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_4,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_4,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_5,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_5,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_6,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_6,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_7,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_7,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_8,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_8,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_9,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_9,
VehicleVendorPermission.PERMISSION_SET_CAR_VENDOR_CATEGORY_10,
VehicleVendorPermission.PERMISSION_GET_CAR_VENDOR_CATEGORY_10
));
private class CarPropertyEventReceiver implements CarPropertyEventCallback {
private VhalEventVerifier mVerifier;
private boolean mStartVerify = false;
CarPropertyEventReceiver(VhalEventVerifier verifier) {
mVerifier = verifier;
}
@Override
public void onChangeEvent(CarPropertyValue carPropertyValue) {
if (mStartVerify) {
mVerifier.verify(carPropertyValue);
}
}
@Override
public void onErrorEvent(final int propertyId, final int zone) {
Assert.fail("Error: propertyId=" + toHexString(propertyId) + " zone=" + zone);
}
// Start verifying events
public void startVerifying() {
mStartVerify = true;
}
}
/**
* This test will use {@link CarPropertyManager#setProperty(Class, int, int, Object)} to turn
* on the HVAC_PROWER and then let Default VHAL to generate HVAC data and verify on-the-fly
* in the test. It is simulating the HVAC actions coming from hard buttons in a car.
* @throws Exception
*/
@Test
public void testHvacHardButtonOperations() throws Exception {
Log.d(TAG, "Prepare HVAC test data");
List<CarPropertyValue> expectedEvents = getExpectedEvents(CAR_HVAC_TEST_JSON);
List<CarPropertyValue> expectedSetEvents = getExpectedEvents(CAR_HVAC_TEST_SET_JSON);
CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
assertNotNull("CarPropertyManager is null", propMgr);
// test set method from android side
for (CarPropertyValue expectedEvent : expectedSetEvents) {
Class valueClass = expectedEvent.getValue().getClass();
propMgr.setProperty(valueClass,
expectedEvent.getPropertyId(),
expectedEvent.getAreaId(),
expectedEvent.getValue());
Thread.sleep(WAIT_FOR_CALLBACK);
CarPropertyValue receivedEvent = propMgr.getProperty(valueClass,
expectedEvent.getPropertyId(), expectedEvent.getAreaId());
assertTrue("Mismatched events, expected: " + expectedEvent + ", received: "
+ receivedEvent, Utils.areCarPropertyValuesEqual(expectedEvent, receivedEvent));
}
// test that set from vehicle side will trigger callback to android
VhalEventVerifier verifier = new VhalEventVerifier(expectedEvents);
ArraySet<Integer> props = new ArraySet<>();
for (CarPropertyValue event : expectedEvents) {
props.add(event.getPropertyId());
}
CarPropertyEventReceiver receiver =
new CarPropertyEventReceiver(verifier);
for (Integer prop : props) {
propMgr.registerCallback(receiver, prop, 0);
}
Thread.sleep(WAIT_FOR_CALLBACK);
receiver.startVerifying();
injectEventFromVehicleSide(expectedEvents, propMgr);
verifier.waitForEnd(TEST_TIME_OUT.toMillis());
propMgr.unregisterCallback(receiver);
assertTrue("Detected mismatched events: " + verifier.getResultString(),
verifier.getMismatchedEvents().isEmpty());
}
/**
* Static properties' value should never be changed.
* @throws Exception
*/
@Test
public void testStaticInfoOperations() throws Exception {
Log.d(TAG, "Prepare static car information");
List<CarPropertyValue> expectedEvents = getExpectedEvents(CAR_INFO_TEST_JSON);
CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
assertNotNull("CarPropertyManager is null", propMgr);
for (CarPropertyValue expectedEvent : expectedEvents) {
CarPropertyValue actualEvent = propMgr.getProperty(
expectedEvent.getPropertyId(), expectedEvent.getAreaId());
assertTrue(String.format(
"Mismatched car information data, actual: %s, expected: %s",
actualEvent, expectedEvent),
Utils.areCarPropertyValuesEqual(actualEvent, expectedEvent));
}
}
/**
* This test will test set/get on MIX type properties. It needs a vendor property in Google
* Vehicle HAL. See kMixedTypePropertyForTest in google defaultConfig.h for details.
* @throws Exception
*/
@Test
public void testMixedTypeProperty() throws Exception {
CarPropertyManager propertyManager =
(CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
ArraySet<Integer> propConfigSet = new ArraySet<>();
propConfigSet.add(MIXED_TYPE_PROPERTY);
List<CarPropertyConfig> configs = propertyManager.getPropertyList(propConfigSet);
// use google HAL in the test
assertNotEquals("Can not find MIXED type properties in HAL",
0, configs.size());
// test CarPropertyConfig
CarPropertyConfig<?> cfg = configs.get(0);
List<Integer> configArrayExpected = Arrays.asList(1, 1, 0, 2, 0, 0, 1, 0, 0);
assertArrayEquals(configArrayExpected.toArray(), cfg.getConfigArray().toArray());
// test SET/GET methods
CarPropertyValue<Object[]> propertyValue = propertyManager.getProperty(Object[].class,
MIXED_TYPE_PROPERTY, 0);
assertArrayEquals(DEFAULT_VALUE, propertyValue.getValue());
Object[] expectedValue = {"MIXED property", false, 5, 4, 3.2f};
propertyManager.setProperty(Object[].class, MIXED_TYPE_PROPERTY, 0, expectedValue);
// Wait for VHAL
Thread.sleep(WAIT_FOR_CALLBACK);
CarPropertyValue<Object[]> result = propertyManager.getProperty(Object[].class,
MIXED_TYPE_PROPERTY, 0);
assertArrayEquals(expectedValue, result.getValue());
}
/**
* This test will test the case: vehicle events comes to android out of order.
* See the events in car_property_test.json.
* @throws Exception
*/
@Test
public void testPropertyEventOutOfOrder() throws Exception {
CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
assertNotNull("CarPropertyManager is null", propMgr);
List<CarPropertyValue> expectedEvents = getExpectedEvents(CAR_PROPERTY_TEST_JSON);
GearEventTestCallback cb = new GearEventTestCallback();
propMgr.registerCallback(cb, GEAR_PROPERTY_ID, CarPropertyManager.SENSOR_RATE_ONCHANGE);
injectEventFromVehicleSide(expectedEvents, propMgr);
// check VHAL ignored the last event in car_property_test, because it is out of order.
int currentGear = propMgr.getIntProperty(GEAR_PROPERTY_ID, 0);
assertEquals(16, currentGear);
}
/**
* Check only vendor properties have vendor permissions.
*/
@Test
public void checkPropertyPermission() {
CarPropertyManager propMgr = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
List<CarPropertyConfig> configs = propMgr.getPropertyList();
for (CarPropertyConfig cfg : configs) {
String readPermission = propMgr.getReadPermission(cfg.getPropertyId());
String writePermission = propMgr.getWritePermission(cfg.getPropertyId());
if ((cfg.getPropertyId() & VehiclePropertyGroup.MASK) == VehiclePropertyGroup.VENDOR) {
Assert.assertTrue(readPermission == null
|| VENDOR_PERMISSIONS.contains(readPermission));
Assert.assertTrue(writePermission == null
|| VENDOR_PERMISSIONS.contains(writePermission));
} else {
Assert.assertTrue(readPermission == null
|| !VENDOR_PERMISSIONS.contains(readPermission));
Assert.assertTrue(writePermission == null
|| !VENDOR_PERMISSIONS.contains(writePermission));
}
}
}
private class GearEventTestCallback implements CarPropertyEventCallback {
private long mTimestamp = 0L;
@Override
public void onChangeEvent(CarPropertyValue carPropertyValue) {
if (carPropertyValue.getPropertyId() != GEAR_PROPERTY_ID) {
return;
}
if (carPropertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE) {
Assert.assertTrue("Received events out of oder",
mTimestamp <= carPropertyValue.getTimestamp());
mTimestamp = carPropertyValue.getTimestamp();
}
}
@Override
public void onErrorEvent(final int propertyId, final int zone) {
Assert.fail("Error: propertyId: x0" + toHexString(propertyId) + " areaId: " + zone);
}
}
/**
* Inject events from vehicle side. It change the value of property even the property is a
* read_only property such as GEAR_SELECTION. It only works with Google VHAL.
*/
private void injectEventFromVehicleSide(List<CarPropertyValue> expectedEvents,
CarPropertyManager propMgr) {
for (CarPropertyValue propertyValue : expectedEvents) {
Object[] values = new Object[3];
int propId;
// The order of values is matter
if (propertyValue.getValue() instanceof Integer) {
propId = SET_INT_FROM_VEHICLE;
values[0] = propertyValue.getPropertyId();
values[1] = propertyValue.getValue();
values[2] = propertyValue.getTimestamp() + SystemClock.elapsedRealtimeNanos();
} else if (propertyValue.getValue() instanceof Float) {
values[0] = propertyValue.getPropertyId();
values[1] = propertyValue.getTimestamp() + SystemClock.elapsedRealtimeNanos();
values[2] = propertyValue.getValue();
propId = SET_FLOAT_FROM_VEHICLE;
} else if (propertyValue.getValue() instanceof Boolean) {
propId = SET_BOOLEAN_FROM_VEHICLE;
values[1] = propertyValue.getPropertyId();
values[0] = propertyValue.getValue();
values[2] = propertyValue.getTimestamp() + SystemClock.elapsedRealtimeNanos();
} else {
throw new IllegalArgumentException(
"Unexpected property type for property " + propertyValue.getPropertyId());
}
propMgr.setProperty(Object[].class, propId, propertyValue.getAreaId(), values);
}
}
}