| /* |
| * Copyright (C) 2019 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; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.fail; |
| import static org.testng.Assert.assertThrows; |
| |
| import android.car.Car; |
| import android.car.VehicleAreaType; |
| import android.car.VehiclePropertyIds; |
| import android.car.hardware.CarPropertyConfig; |
| import android.car.hardware.CarPropertyValue; |
| import android.car.hardware.property.CarInternalErrorException; |
| import android.car.hardware.property.CarPropertyManager; |
| import android.car.hardware.property.PropertyAccessDeniedSecurityException; |
| import android.car.hardware.property.PropertyNotAvailableAndRetryException; |
| import android.car.hardware.property.PropertyNotAvailableException; |
| import android.car.hardware.property.VehicleHalStatusCode; |
| import android.car.test.util.Visitor; |
| import android.hardware.automotive.vehicle.V2_0.VehicleArea; |
| import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType; |
| import android.hardware.automotive.vehicle.V2_0.VehicleVendorPermission; |
| import android.os.Build; |
| import android.os.ServiceSpecificException; |
| import android.os.SystemClock; |
| import android.util.ArraySet; |
| import android.util.Log; |
| |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.MediumTest; |
| |
| import com.android.car.vehiclehal.test.HidlMockedVehicleHal.VehicleHalPropertyHandler; |
| |
| import com.google.common.truth.Truth; |
| |
| import org.junit.Assert; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TestName; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Test for {@link android.car.hardware.property.CarPropertyManager} |
| */ |
| @RunWith(AndroidJUnit4.class) |
| @MediumTest |
| public class CarPropertyManagerTest extends MockedCarTestBase { |
| |
| private static final String TAG = CarPropertyManagerTest.class.getSimpleName(); |
| |
| /** |
| * configArray[0], 1 indicates the property has a String value |
| * configArray[1], 1 indicates the property has a Boolean value . |
| * configArray[2], 1 indicates the property has a Integer value |
| * configArray[3], the number indicates the size of Integer[] in the property. |
| * configArray[4], 1 indicates the property has a Long value . |
| * configArray[5], the number indicates the size of Long[] in the property. |
| * configArray[6], 1 indicates the property has a Float value . |
| * configArray[7], the number indicates the size of Float[] in the property. |
| * configArray[8], the number indicates the size of byte[] in the property. |
| */ |
| private static final java.util.Collection<Integer> CONFIG_ARRAY_1 = |
| Arrays.asList(1, 0, 1, 0, 1, 0, 0, 0, 0); |
| private static final java.util.Collection<Integer> CONFIG_ARRAY_2 = |
| Arrays.asList(1, 1, 1, 0, 0, 0, 0, 2, 0); |
| private static final java.util.Collection<Integer> CONFIG_ARRAY_3 = |
| Arrays.asList(0, 1, 1, 0, 0, 0, 1, 0, 0); |
| private static final Object[] EXPECTED_VALUE_1 = {"android", 1, 1L}; |
| private static final Object[] EXPECTED_VALUE_2 = {"android", true, 3, 1.1f, 2f}; |
| private static final Object[] EXPECTED_VALUE_3 = {true, 1, 2.2f}; |
| |
| private static final int CUSTOM_SEAT_INT_PROP_1 = |
| 0x1201 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.SEAT; |
| private static final int CUSTOM_SEAT_INT_PROP_2 = |
| 0x1202 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.SEAT; |
| |
| private static final int CUSTOM_SEAT_MIXED_PROP_ID_1 = |
| 0x1101 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.MIXED | VehicleArea.SEAT; |
| private static final int CUSTOM_GLOBAL_MIXED_PROP_ID_2 = |
| 0x1102 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.MIXED | VehicleArea.GLOBAL; |
| private static final int CUSTOM_GLOBAL_MIXED_PROP_ID_3 = |
| 0x1110 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.MIXED | VehicleArea.GLOBAL; |
| |
| private static final int CUSTOM_GLOBAL_INT_ARRAY_PROP = |
| 0x1103 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32_VEC |
| | VehicleArea.GLOBAL; |
| private static final Integer[] FAKE_INT_ARRAY_VALUE = {1, 2}; |
| |
| private static final int INT_ARRAY_PROP_STATUS_ERROR = |
| 0x1104 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32_VEC |
| | VehicleArea.GLOBAL; |
| |
| private static final int BOOLEAN_PROP_STATUS_ERROR = |
| 0x1105 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.BOOLEAN |
| | VehicleArea.GLOBAL; |
| private static final boolean FAKE_BOOLEAN_PROPERTY_VALUE = true; |
| private static final int FLOAT_PROP_STATUS_UNAVAILABLE = |
| 0x1106 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.FLOAT |
| | VehicleArea.GLOBAL; |
| private static final float FAKE_FLOAT_PROPERTY_VALUE = 3f; |
| private static final int INT_PROP_STATUS_UNAVAILABLE = |
| 0x1107 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 |
| | VehicleArea.GLOBAL; |
| private static final int FAKE_INT_PROPERTY_VALUE = 3; |
| // A property that always returns null to simulate an unavailable property. |
| private static final int NULL_VALUE_PROP = |
| 0x1108 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 |
| | VehicleArea.GLOBAL; |
| |
| // Vendor properties for testing exceptions. |
| private static final int PROP_CAUSE_STATUS_CODE_TRY_AGAIN = |
| 0x1201 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL; |
| private static final int PROP_CAUSE_STATUS_CODE_INVALID_ARG = |
| 0x1202 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL; |
| private static final int PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE = |
| 0x1203 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL; |
| private static final int PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR = |
| 0x1204 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL; |
| private static final int PROP_CAUSE_STATUS_CODE_ACCESS_DENIED = |
| 0x1205 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL; |
| |
| // Vendor properties for testing permissions |
| private static final int PROP_WITH_READ_ONLY_PERMISSION = |
| 0x1301 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL; |
| private static final int PROP_WITH_WRITE_ONLY_PERMISSION = |
| 0x1302 | VehiclePropertyGroup.VENDOR | VehiclePropertyType.INT32 | VehicleArea.GLOBAL; |
| private static final int SUPPORT_CUSTOM_PERMISSION = 287313669; |
| private static final java.util.Collection<Integer> VENDOR_PERMISSION_CONFIG = |
| Collections.unmodifiableList( |
| Arrays.asList(PROP_WITH_READ_ONLY_PERMISSION, |
| VehicleVendorPermission.PERMISSION_GET_VENDOR_CATEGORY_1, |
| VehicleVendorPermission.PERMISSION_NOT_ACCESSIBLE, |
| PROP_WITH_WRITE_ONLY_PERMISSION, |
| VehicleVendorPermission.PERMISSION_NOT_ACCESSIBLE, |
| VehicleVendorPermission.PERMISSION_SET_VENDOR_CATEGORY_1)); |
| |
| |
| // Use FAKE_PROPERTY_ID to test api return null or throw exception. |
| private static final int FAKE_PROPERTY_ID = 0x111; |
| |
| private static final int DRIVER_SIDE_AREA_ID = VehicleAreaSeat.ROW_1_LEFT |
| | VehicleAreaSeat.ROW_2_LEFT; |
| private static final int PASSENGER_SIDE_AREA_ID = VehicleAreaSeat.ROW_1_RIGHT |
| | VehicleAreaSeat.ROW_2_CENTER |
| | VehicleAreaSeat.ROW_2_RIGHT; |
| private static final float INIT_TEMP_VALUE = 16f; |
| private static final float CHANGED_TEMP_VALUE = 20f; |
| private static final int CALLBACK_SHORT_TIMEOUT_MS = 350; // ms |
| // Wait for CarPropertyManager register/unregister listener |
| private static final long WAIT_FOR_NO_EVENTS = 50; |
| |
| private static final List<Integer> USER_HAL_PROPERTIES = Arrays.asList( |
| VehiclePropertyIds.INITIAL_USER_INFO, |
| VehiclePropertyIds.SWITCH_USER, |
| VehiclePropertyIds.CREATE_USER, |
| VehiclePropertyIds.REMOVE_USER, |
| VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION |
| ); |
| |
| private CarPropertyManager mManager; |
| |
| @Rule public TestName mTestName = new TestName(); |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| setUpTargetSdk(); |
| mManager = (CarPropertyManager) getCar().getCarManager(Car.PROPERTY_SERVICE); |
| assertThat(mManager).isNotNull(); |
| } |
| |
| private void setUpTargetSdk() { |
| if (mTestName.getMethodName().endsWith("InQ")) { |
| getContext().getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.Q; |
| } else if (mTestName.getMethodName().endsWith("AfterQ")) { |
| getContext().getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.R; |
| } else if (mTestName.getMethodName().endsWith("AfterR")) { |
| getContext().getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.S; |
| } |
| } |
| |
| @Test |
| public void testMixedPropertyConfigs() { |
| List<CarPropertyConfig> configs = mManager.getPropertyList(); |
| for (CarPropertyConfig cfg : configs) { |
| switch (cfg.getPropertyId()) { |
| case CUSTOM_SEAT_MIXED_PROP_ID_1: |
| assertThat(cfg.getConfigArray()).containsExactlyElementsIn(CONFIG_ARRAY_1) |
| .inOrder(); |
| break; |
| case CUSTOM_GLOBAL_MIXED_PROP_ID_2: |
| assertThat(cfg.getConfigArray()).containsExactlyElementsIn(CONFIG_ARRAY_2) |
| .inOrder(); |
| break; |
| case CUSTOM_GLOBAL_MIXED_PROP_ID_3: |
| assertThat(cfg.getConfigArray()).containsExactlyElementsIn(CONFIG_ARRAY_3) |
| .inOrder(); |
| break; |
| case VehiclePropertyIds.HVAC_TEMPERATURE_SET: |
| case PROP_CAUSE_STATUS_CODE_ACCESS_DENIED: |
| case PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR: |
| case PROP_CAUSE_STATUS_CODE_TRY_AGAIN: |
| case PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE: |
| case PROP_CAUSE_STATUS_CODE_INVALID_ARG: |
| case CUSTOM_SEAT_INT_PROP_1: |
| case CUSTOM_SEAT_INT_PROP_2: |
| case CUSTOM_GLOBAL_INT_ARRAY_PROP: |
| case INT_ARRAY_PROP_STATUS_ERROR: |
| case BOOLEAN_PROP_STATUS_ERROR: |
| case INT_PROP_STATUS_UNAVAILABLE: |
| case FLOAT_PROP_STATUS_UNAVAILABLE: |
| case VehiclePropertyIds.INFO_VIN: |
| case NULL_VALUE_PROP: |
| case SUPPORT_CUSTOM_PERMISSION: |
| case PROP_WITH_READ_ONLY_PERMISSION: |
| case PROP_WITH_WRITE_ONLY_PERMISSION: |
| break; |
| default: |
| Assert.fail("Unexpected CarPropertyConfig: " + cfg.toString()); |
| } |
| } |
| } |
| |
| @Test |
| public void testGetMixTypeProperty() { |
| mManager.setProperty(Object[].class, CUSTOM_SEAT_MIXED_PROP_ID_1, |
| 0, EXPECTED_VALUE_1); |
| CarPropertyValue<Object[]> result = mManager.getProperty( |
| CUSTOM_SEAT_MIXED_PROP_ID_1, 0); |
| assertThat(result.getValue()).isEqualTo(EXPECTED_VALUE_1); |
| |
| mManager.setProperty(Object[].class, CUSTOM_GLOBAL_MIXED_PROP_ID_2, |
| 0, EXPECTED_VALUE_2); |
| result = mManager.getProperty( |
| CUSTOM_GLOBAL_MIXED_PROP_ID_2, 0); |
| assertThat(result.getValue()).isEqualTo(EXPECTED_VALUE_2); |
| |
| mManager.setProperty(Object[].class, CUSTOM_GLOBAL_MIXED_PROP_ID_3, |
| 0, EXPECTED_VALUE_3); |
| result = mManager.getProperty( |
| CUSTOM_GLOBAL_MIXED_PROP_ID_3, 0); |
| assertThat(result.getValue()).isEqualTo(EXPECTED_VALUE_3); |
| } |
| |
| /** |
| * Test {@link android.car.hardware.property.CarPropertyManager#getIntArrayProperty(int, int)} |
| */ |
| @Test |
| public void testGetIntArrayProperty() { |
| mManager.setProperty(Integer[].class, CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL, |
| FAKE_INT_ARRAY_VALUE); |
| |
| int[] result = mManager.getIntArrayProperty(CUSTOM_GLOBAL_INT_ARRAY_PROP, |
| VehicleArea.GLOBAL); |
| assertThat(result).asList().containsExactlyElementsIn(FAKE_INT_ARRAY_VALUE); |
| } |
| |
| /** |
| * Test {@link CarPropertyManager#getIntArrayProperty(int, int)} when vhal returns a value with |
| * error status. |
| */ |
| @Test |
| public void testGetIntArrayPropertyWithErrorStatusAfterR() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isGreaterThan(Build.VERSION_CODES.R); |
| mManager.setProperty(Integer[].class, INT_ARRAY_PROP_STATUS_ERROR, |
| VehicleArea.GLOBAL, FAKE_INT_ARRAY_VALUE); |
| assertThrows(CarInternalErrorException.class, |
| () -> mManager.getIntArrayProperty(INT_ARRAY_PROP_STATUS_ERROR, |
| VehicleArea.GLOBAL)); |
| } |
| |
| /** |
| * Test {@link CarPropertyManager#getIntProperty(int, int)} when vhal returns a value with |
| * unavailable status. |
| */ |
| @Test |
| public void testGetIntPropertyWithUnavailableStatusAfterR() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isGreaterThan(Build.VERSION_CODES.R); |
| mManager.setProperty(Integer.class, INT_PROP_STATUS_UNAVAILABLE, |
| VehicleArea.GLOBAL, FAKE_INT_PROPERTY_VALUE); |
| assertThrows(PropertyNotAvailableException.class, |
| () -> mManager.getIntProperty(INT_PROP_STATUS_UNAVAILABLE, VehicleArea.GLOBAL)); |
| |
| } |
| |
| /** |
| * Test {@link CarPropertyManager#getBooleanProperty(int, int)} when vhal returns a value with |
| * error status. |
| */ |
| @Test |
| public void testGetBooleanPropertyWithErrorStatusAfterR() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isGreaterThan(Build.VERSION_CODES.R); |
| mManager.setProperty(Boolean.class, BOOLEAN_PROP_STATUS_ERROR, |
| VehicleArea.GLOBAL, FAKE_BOOLEAN_PROPERTY_VALUE); |
| assertThrows(CarInternalErrorException.class, |
| () -> mManager.getBooleanProperty(BOOLEAN_PROP_STATUS_ERROR, VehicleArea.GLOBAL)); |
| } |
| |
| /** |
| * Test {@link CarPropertyManager#getFloatProperty(int, int)} when vhal returns a value with |
| * unavailable status. |
| */ |
| @Test |
| public void testGetFloatPropertyWithUnavailableStatusAfterR() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isGreaterThan(Build.VERSION_CODES.R); |
| mManager.setProperty(Float.class, FLOAT_PROP_STATUS_UNAVAILABLE, |
| VehicleArea.GLOBAL, FAKE_FLOAT_PROPERTY_VALUE); |
| assertThrows(PropertyNotAvailableException.class, |
| () -> mManager.getFloatProperty(FLOAT_PROP_STATUS_UNAVAILABLE, VehicleArea.GLOBAL)); |
| } |
| |
| /** |
| * Test {@link CarPropertyManager#getProperty(Class, int, int)} |
| */ |
| @Test |
| public void testGetPropertyWithClass() { |
| mManager.setProperty(Integer[].class, CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL, |
| FAKE_INT_ARRAY_VALUE); |
| |
| CarPropertyValue<Integer[]> result = mManager.getProperty(Integer[].class, |
| CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL); |
| assertThat(result.getValue()).asList().containsExactlyElementsIn(FAKE_INT_ARRAY_VALUE); |
| } |
| |
| /** |
| * Test {@link CarPropertyManager#isPropertyAvailable(int, int)} |
| */ |
| @Test |
| public void testIsPropertyAvailable() { |
| assertThat(mManager.isPropertyAvailable(FAKE_PROPERTY_ID, VehicleArea.GLOBAL)).isFalse(); |
| assertThat(mManager.isPropertyAvailable(CUSTOM_GLOBAL_INT_ARRAY_PROP, VehicleArea.GLOBAL)) |
| .isTrue(); |
| } |
| |
| /** |
| * Test {@link CarPropertyManager#getWritePermission(int)} |
| * and {@link CarPropertyManager#getWritePermission(int)} |
| */ |
| @Test |
| public void testGetPermission() { |
| String hvacReadPermission = mManager.getReadPermission( |
| VehiclePropertyIds.HVAC_TEMPERATURE_SET); |
| assertThat(hvacReadPermission).isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE); |
| String hvacWritePermission = mManager.getWritePermission( |
| VehiclePropertyIds.HVAC_TEMPERATURE_SET); |
| assertThat(hvacWritePermission).isEqualTo(Car.PERMISSION_CONTROL_CAR_CLIMATE); |
| |
| // For read-only property |
| String vinReadPermission = mManager.getReadPermission(VehiclePropertyIds.INFO_VIN); |
| assertThat(vinReadPermission).isEqualTo(Car.PERMISSION_IDENTIFICATION); |
| String vinWritePermission = mManager.getWritePermission(VehiclePropertyIds.INFO_VIN); |
| assertThat(vinWritePermission).isNull(); |
| } |
| |
| @Test |
| public void testGetPropertyConfig() { |
| CarPropertyConfig config = mManager.getCarPropertyConfig(CUSTOM_SEAT_MIXED_PROP_ID_1); |
| assertThat(config.getPropertyId()).isEqualTo(CUSTOM_SEAT_MIXED_PROP_ID_1); |
| // return null if can not find the propertyConfig for the property. |
| assertThat(mManager.getCarPropertyConfig(FAKE_PROPERTY_ID)).isNull(); |
| } |
| |
| @Test |
| public void testGetPropertyConfig_withReadOnlyPermission() { |
| CarPropertyConfig configForReadOnlyProperty = mManager |
| .getCarPropertyConfig(PROP_WITH_READ_ONLY_PERMISSION); |
| |
| assertThat(configForReadOnlyProperty).isNotNull(); |
| assertThat(configForReadOnlyProperty.getPropertyId()) |
| .isEqualTo(PROP_WITH_READ_ONLY_PERMISSION); |
| } |
| |
| @Test |
| public void testGetPropertyConfig_withWriteOnlyPermission() { |
| CarPropertyConfig configForWriteOnlyProperty = mManager |
| .getCarPropertyConfig(PROP_WITH_WRITE_ONLY_PERMISSION); |
| |
| assertThat(configForWriteOnlyProperty).isNotNull(); |
| assertThat(configForWriteOnlyProperty.getPropertyId()) |
| .isEqualTo(PROP_WITH_WRITE_ONLY_PERMISSION); |
| } |
| |
| @Test |
| public void testGetAreaId() { |
| int result = mManager.getAreaId(CUSTOM_SEAT_MIXED_PROP_ID_1, VehicleAreaSeat.ROW_1_LEFT); |
| assertThat(result).isEqualTo(DRIVER_SIDE_AREA_ID); |
| //test for the GLOBAL property |
| int globalAreaId = |
| mManager.getAreaId(CUSTOM_GLOBAL_MIXED_PROP_ID_2, VehicleAreaSeat.ROW_1_LEFT); |
| assertThat(globalAreaId).isEqualTo(VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL); |
| //test exception |
| assertThrows(IllegalArgumentException.class, () -> mManager.getAreaId( |
| CUSTOM_SEAT_MIXED_PROP_ID_1, VehicleAreaSeat.ROW_3_CENTER)); |
| assertThrows(IllegalArgumentException.class, () -> mManager.getAreaId(FAKE_PROPERTY_ID, |
| VehicleAreaSeat.ROW_1_LEFT)); |
| } |
| |
| @Test |
| public void testRegisterPropertyUnavailable() throws Exception { |
| TestSequenceCallback callback = new TestSequenceCallback(1); |
| // Registering a property which has an unavailable initial value |
| // won't throw ServiceSpecificException. |
| mManager.registerCallback(callback, PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, |
| CarPropertyManager.SENSOR_RATE_ONCHANGE); |
| // initial value is unavailable, should not get any callback. |
| assertThrows(IllegalStateException.class, callback::assertOnChangeEventCalled); |
| } |
| |
| @Test |
| public void testNotReceiveOnErrorEvent() throws Exception { |
| TestErrorCallback callback = new TestErrorCallback(); |
| mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET, |
| CarPropertyManager.SENSOR_RATE_ONCHANGE); |
| callback.assertRegisterCompleted(); |
| injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID, |
| CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN); |
| // app never change the value of HVAC_TEMPERATURE_SET, it won't get an error code. |
| callback.assertOnErrorEventNotCalled(); |
| } |
| |
| @Test |
| public void testReceiveOnErrorEvent() throws Exception { |
| TestErrorCallback callback = new TestErrorCallback(); |
| mManager.registerCallback(callback, VehiclePropertyIds.HVAC_TEMPERATURE_SET, |
| CarPropertyManager.SENSOR_RATE_ONCHANGE); |
| callback.assertRegisterCompleted(); |
| mManager.setFloatProperty( |
| VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID, |
| CHANGED_TEMP_VALUE); |
| injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID, |
| CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN); |
| callback.assertOnErrorEventCalled(); |
| assertThat(callback.mReceivedErrorEventWithErrorCode).isTrue(); |
| assertThat(callback.mErrorCode).isEqualTo( |
| CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN); |
| assertThat(callback.mReceivedErrorEventWithOutErrorCode).isFalse(); |
| } |
| |
| @Test |
| public void testNotReceiveOnErrorEventAfterUnregister() throws Exception { |
| TestErrorCallback callback1 = new TestErrorCallback(); |
| mManager.registerCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET, |
| CarPropertyManager.SENSOR_RATE_ONCHANGE); |
| callback1.assertRegisterCompleted(); |
| TestErrorCallback callback2 = new TestErrorCallback(); |
| mManager.registerCallback(callback2, VehiclePropertyIds.HVAC_TEMPERATURE_SET, |
| CarPropertyManager.SENSOR_RATE_ONCHANGE); |
| mManager.setFloatProperty( |
| VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID, |
| CHANGED_TEMP_VALUE); |
| mManager.unregisterCallback(callback1, VehiclePropertyIds.HVAC_TEMPERATURE_SET); |
| SystemClock.sleep(WAIT_FOR_NO_EVENTS); |
| injectErrorEvent(VehiclePropertyIds.HVAC_TEMPERATURE_SET, PASSENGER_SIDE_AREA_ID, |
| CarPropertyManager.CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN); |
| // callback1 is unregistered |
| callback1.assertOnErrorEventNotCalled(); |
| callback2.assertOnErrorEventCalled(); |
| } |
| @Test |
| public void testSetterExceptionsInQ() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isEqualTo(Build.VERSION_CODES.Q); |
| |
| assertThrows(IllegalStateException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(IllegalStateException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(IllegalStateException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(IllegalArgumentException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INVALID_ARG, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(RuntimeException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| } |
| |
| @Test |
| public void testSetterExceptionsAfterQ() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isGreaterThan(Build.VERSION_CODES.Q); |
| |
| assertThrows(PropertyAccessDeniedSecurityException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(PropertyNotAvailableAndRetryException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_TRY_AGAIN, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(PropertyNotAvailableException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(CarInternalErrorException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| assertThrows(IllegalArgumentException.class, |
| ()->mManager.setProperty(Integer.class, PROP_CAUSE_STATUS_CODE_INVALID_ARG, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 1)); |
| } |
| |
| @Test |
| public void testGetterExceptionsInQ() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isEqualTo(Build.VERSION_CODES.Q); |
| |
| assertThrows(IllegalStateException.class, |
| ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(IllegalStateException.class, |
| ()->mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(IllegalArgumentException.class, |
| ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(IllegalArgumentException.class, |
| ()->mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(IllegalStateException.class, |
| ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(IllegalStateException.class, |
| ()->mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(IllegalStateException.class, |
| ()->mManager.getProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(IllegalStateException.class, |
| ()->mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(IllegalStateException.class, |
| ()->mManager.getProperty(NULL_VALUE_PROP, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| Truth.assertThat(mManager.getProperty(PROP_CAUSE_STATUS_CODE_TRY_AGAIN, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)).isNull(); |
| |
| } |
| |
| @Test |
| public void testGetterExceptionsAfterQ() { |
| Truth.assertThat(getContext().getApplicationInfo().targetSdkVersion) |
| .isAtLeast(Build.VERSION_CODES.R); |
| |
| assertThrows(PropertyAccessDeniedSecurityException.class, |
| () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(PropertyAccessDeniedSecurityException.class, |
| () -> mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(IllegalArgumentException.class, |
| () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(IllegalArgumentException.class, |
| () -> mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(PropertyNotAvailableAndRetryException.class, |
| () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_TRY_AGAIN, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(PropertyNotAvailableAndRetryException.class, |
| () -> mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_TRY_AGAIN, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(PropertyNotAvailableException.class, |
| () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(PropertyNotAvailableException.class, |
| () -> mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(CarInternalErrorException.class, |
| () -> mManager.getProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| assertThrows(CarInternalErrorException.class, |
| () -> mManager.getIntProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| |
| assertThrows(PropertyNotAvailableException.class, |
| ()->mManager.getProperty(NULL_VALUE_PROP, |
| VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL)); |
| } |
| |
| @Test |
| public void testOnChangeEventWithSameAreaId() throws Exception { |
| // init |
| mManager.setProperty(Integer.class, |
| CUSTOM_SEAT_INT_PROP_1, DRIVER_SIDE_AREA_ID, 1); |
| TestSequenceCallback callback = new TestSequenceCallback(1); |
| mManager.registerCallback(callback, CUSTOM_SEAT_INT_PROP_1, 0); |
| callback.assertRegisterCompleted(); |
| |
| VehiclePropValue firstFakeValueDriveSide = new VehiclePropValue(); |
| firstFakeValueDriveSide.prop = CUSTOM_SEAT_INT_PROP_1; |
| firstFakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID; |
| firstFakeValueDriveSide.value.int32Values.add(2); |
| firstFakeValueDriveSide.timestamp = SystemClock.elapsedRealtimeNanos(); |
| VehiclePropValue secFakeValueDriveSide = new VehiclePropValue(); |
| secFakeValueDriveSide.prop = CUSTOM_SEAT_INT_PROP_1; |
| secFakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID; |
| secFakeValueDriveSide.value.int32Values.add(3); // 0 in HAL indicate false; |
| secFakeValueDriveSide.timestamp = SystemClock.elapsedRealtimeNanos(); |
| // inject the new event first |
| getHidlMockedVehicleHal().injectEvent(secFakeValueDriveSide); |
| // inject the old event |
| getHidlMockedVehicleHal().injectEvent(firstFakeValueDriveSide); |
| callback.assertOnChangeEventCalled(); |
| // Client should only get the new event |
| assertThat((int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_1).getValue()) |
| .isEqualTo(3); |
| assertThat(callback.getEventCounter()).isEqualTo(1); |
| |
| } |
| |
| @Test |
| public void testOnChangeEventWithDifferentAreaId() throws Exception { |
| // init |
| mManager.setProperty(Integer.class, |
| CUSTOM_SEAT_INT_PROP_2, DRIVER_SIDE_AREA_ID, 1); |
| TestSequenceCallback callback = new TestSequenceCallback(2); |
| mManager.registerCallback(callback, CUSTOM_SEAT_INT_PROP_2, 0); |
| callback.assertRegisterCompleted(); |
| VehiclePropValue fakeValueDriveSide = new VehiclePropValue(); |
| fakeValueDriveSide.prop = CUSTOM_SEAT_INT_PROP_2; |
| fakeValueDriveSide.areaId = DRIVER_SIDE_AREA_ID; |
| fakeValueDriveSide.value.int32Values.add(4); |
| fakeValueDriveSide.timestamp = SystemClock.elapsedRealtimeNanos(); |
| |
| VehiclePropValue fakeValuePsgSide = new VehiclePropValue(); |
| fakeValuePsgSide.prop = CUSTOM_SEAT_INT_PROP_2; |
| fakeValuePsgSide.areaId = PASSENGER_SIDE_AREA_ID; |
| fakeValuePsgSide.value.int32Values.add(5); |
| fakeValuePsgSide.timestamp = SystemClock.elapsedRealtimeNanos(); |
| |
| // inject passenger event before driver event |
| getHidlMockedVehicleHal().injectEvent(fakeValuePsgSide); |
| getHidlMockedVehicleHal().injectEvent(fakeValueDriveSide); |
| callback.assertOnChangeEventCalled(); |
| |
| // both events should be received by listener |
| assertThat((int) callback.getLastCarPropertyValue(CUSTOM_SEAT_INT_PROP_2).getValue()) |
| .isEqualTo(4); |
| assertThat(callback.getEventCounter()).isEqualTo(2); |
| } |
| |
| @Test |
| public void testOnChangeEventInvalidPayload() throws Exception { |
| // init |
| mManager.setProperty(Integer.class, CUSTOM_SEAT_INT_PROP_1, DRIVER_SIDE_AREA_ID, 1); |
| TestSequenceCallback callback = new TestSequenceCallback(0); |
| mManager.registerCallback(callback, CUSTOM_SEAT_INT_PROP_1, 0); |
| callback.assertRegisterCompleted(); |
| |
| List<VehiclePropValue> props = new ArrayList<VehiclePropValue>(); |
| VehiclePropValue emptyProp = new VehiclePropValue(); |
| emptyProp.prop = CUSTOM_SEAT_INT_PROP_1; |
| props.add(emptyProp); |
| |
| VehiclePropValue twoIntsProp = new VehiclePropValue(); |
| twoIntsProp.prop = CUSTOM_SEAT_INT_PROP_1; |
| twoIntsProp.value.int32Values.add(0); |
| twoIntsProp.value.int32Values.add(1); |
| props.add(twoIntsProp); |
| |
| VehiclePropValue propWithFloat = new VehiclePropValue(); |
| propWithFloat.prop = CUSTOM_SEAT_INT_PROP_1; |
| propWithFloat.value.floatValues.add((float) 0.0); |
| props.add(propWithFloat); |
| |
| VehiclePropValue propWithString = new VehiclePropValue(); |
| propWithString.prop = CUSTOM_SEAT_INT_PROP_1; |
| propWithString.value.stringValue = "1234"; |
| props.add(propWithString); |
| |
| for (VehiclePropValue prop: props) { |
| // inject passenger event before driver event |
| getHidlMockedVehicleHal().injectEvent(prop); |
| assertThat(callback.getEventCounter()).isEqualTo(0); |
| } |
| } |
| |
| @Test |
| public void testUserHal_getProperty() { |
| userHalPropertiesTest("getProperty()", (prop) -> |
| mManager.getProperty(prop, /* areaId= */ 0)); |
| } |
| |
| @Test |
| public void testUserHal_getBooleanProperty() { |
| userHalPropertiesTest("getBooleanProperty()", (prop) -> |
| mManager.getBooleanProperty(prop, /* areaId= */ 0)); |
| } |
| |
| @Test |
| public void testUserHal_getIntProperty() { |
| userHalPropertiesTest("getIntProperty()", (prop) -> |
| mManager.getIntProperty(prop, /* areaId= */ 0)); |
| } |
| |
| @Test |
| public void testUserHal_getIntArrayProperty() { |
| userHalPropertiesTest("getIntArrayProperty()", (prop) -> |
| mManager.getIntArrayProperty(prop, /* areaId= */ 0)); |
| } |
| |
| @Test |
| public void testUserHal_getFloatProperty() { |
| userHalPropertiesTest("getFloatProperty()", (prop) -> |
| mManager.getFloatProperty(prop, /* areaId= */ 0)); |
| } |
| |
| @Test |
| public void testUserHal_getPropertyList() { |
| userHalPropertiesTest("getPropertyList()", (prop) -> { |
| ArraySet<Integer> list = new ArraySet<>(); |
| list.add(prop); |
| mManager.getPropertyList(list); |
| }); |
| } |
| |
| @Test |
| public void testUserHal_getCarPropertyConfig() { |
| userHalPropertiesTest("getCarPropertyConfig()", (prop) -> |
| mManager.getCarPropertyConfig(prop)); |
| } |
| |
| @Test |
| public void testUserHal_getAreaId() { |
| userHalPropertiesTest("getAreaId()", (prop) -> |
| mManager.getAreaId(prop, /* areaId= */ 0)); |
| } |
| |
| @Test |
| public void testUserHal_getReadPermission() { |
| userHalPropertiesTest("getReadPermission()", (prop) -> |
| mManager.getReadPermission(prop)); |
| } |
| |
| @Test |
| public void testUserHal_getWritePermission() { |
| userHalPropertiesTest("getWritePermission()", (prop) -> |
| mManager.getWritePermission(prop)); |
| } |
| |
| @Test |
| public void testUserHal_isPropertyAvailable() { |
| userHalPropertiesTest("isPropertyAvailable()", (prop) -> |
| mManager.isPropertyAvailable(prop, /* area= */ 0)); |
| } |
| |
| @Test |
| public void testUserHal_setProperty() { |
| userHalPropertiesTest("setProperty()", (prop) -> |
| mManager.setProperty(Object.class, prop, /* areaId= */ 0, /* val= */ null)); |
| } |
| |
| @Test |
| public void testUserHal_setBooleanProperty() { |
| userHalPropertiesTest("setBooleanProperty()", (prop) -> |
| mManager.setBooleanProperty(prop, /* areaId= */ 0, /* val= */ true)); |
| } |
| |
| @Test |
| public void testUserHal_setFloatProperty() { |
| userHalPropertiesTest("setFloatProperty()", (prop) -> |
| mManager.setFloatProperty(prop, /* areaId= */ 0, /* val= */ 0.0F)); |
| } |
| |
| @Test |
| public void testUserHal_setIntProperty() { |
| userHalPropertiesTest("setIntProperty()", (prop) -> |
| mManager.setIntProperty(prop, /* areaId= */ 0, /* val= */ 0)); |
| } |
| |
| private void userHalPropertiesTest(String method, Visitor<Integer> visitor) { |
| List<String> failedProperties = new ArrayList<String>(); |
| for (int propertyId : USER_HAL_PROPERTIES) { |
| try { |
| visitor.visit(propertyId); |
| failedProperties.add(propToString(propertyId)); |
| } catch (IllegalArgumentException e) { |
| // expected |
| } |
| } |
| if (!failedProperties.isEmpty()) { |
| fail(method + " should not support these properties: " + failedProperties); |
| } |
| } |
| |
| @Override |
| protected synchronized void configureMockedHal() { |
| PropertyHandler handler = new PropertyHandler(); |
| addProperty(CUSTOM_SEAT_MIXED_PROP_ID_1, handler).setConfigArray(CONFIG_ARRAY_1) |
| .addAreaConfig(DRIVER_SIDE_AREA_ID).addAreaConfig(PASSENGER_SIDE_AREA_ID); |
| addProperty(CUSTOM_GLOBAL_MIXED_PROP_ID_2, handler).setConfigArray(CONFIG_ARRAY_2); |
| addProperty(CUSTOM_GLOBAL_MIXED_PROP_ID_3, handler).setConfigArray(CONFIG_ARRAY_3); |
| addProperty(CUSTOM_GLOBAL_INT_ARRAY_PROP, handler); |
| |
| addProperty(INT_ARRAY_PROP_STATUS_ERROR, handler); |
| addProperty(INT_PROP_STATUS_UNAVAILABLE, handler); |
| addProperty(FLOAT_PROP_STATUS_UNAVAILABLE, handler); |
| addProperty(BOOLEAN_PROP_STATUS_ERROR, handler); |
| |
| VehiclePropValue tempValue = new VehiclePropValue(); |
| tempValue.value.floatValues.add(INIT_TEMP_VALUE); |
| tempValue.prop = VehiclePropertyIds.HVAC_TEMPERATURE_SET; |
| addProperty(VehiclePropertyIds.HVAC_TEMPERATURE_SET, tempValue) |
| .addAreaConfig(DRIVER_SIDE_AREA_ID).addAreaConfig(PASSENGER_SIDE_AREA_ID); |
| addProperty(VehiclePropertyIds.INFO_VIN); |
| |
| addProperty(PROP_CAUSE_STATUS_CODE_ACCESS_DENIED, handler); |
| addProperty(PROP_CAUSE_STATUS_CODE_TRY_AGAIN, handler); |
| addProperty(PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR, handler); |
| addProperty(PROP_CAUSE_STATUS_CODE_INVALID_ARG, handler); |
| addProperty(PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE, handler); |
| |
| addProperty(CUSTOM_SEAT_INT_PROP_1, handler).addAreaConfig(DRIVER_SIDE_AREA_ID) |
| .addAreaConfig(PASSENGER_SIDE_AREA_ID); |
| addProperty(CUSTOM_SEAT_INT_PROP_2, handler).addAreaConfig(DRIVER_SIDE_AREA_ID) |
| .addAreaConfig(PASSENGER_SIDE_AREA_ID); |
| |
| addProperty(NULL_VALUE_PROP, handler); |
| |
| // Add properties for permission testing. |
| addProperty(SUPPORT_CUSTOM_PERMISSION, handler).setConfigArray(VENDOR_PERMISSION_CONFIG); |
| addProperty(PROP_WITH_READ_ONLY_PERMISSION, handler); |
| addProperty(PROP_WITH_WRITE_ONLY_PERMISSION, handler); |
| } |
| |
| private class PropertyHandler implements VehicleHalPropertyHandler { |
| HashMap<Integer, VehiclePropValue> mMap = new HashMap<>(); |
| @Override |
| public synchronized void onPropertySet(VehiclePropValue value) { |
| // Simulate HalClient.set() behavior. |
| int statusCode = mapPropertyToVhalStatusCode(value.prop); |
| if (statusCode != VehicleHalStatusCode.STATUS_OK) { |
| // The ServiceSpecificException here would pass the statusCode back to caller. |
| throw new ServiceSpecificException(statusCode); |
| } |
| |
| mMap.put(value.prop, value); |
| } |
| |
| @Override |
| public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) { |
| // Simulate HalClient.get() behavior. |
| int vhalStatusCode = mapPropertyToVhalStatusCode(value.prop); |
| if (vhalStatusCode != VehicleHalStatusCode.STATUS_OK) { |
| // The ServiceSpecificException here would pass the statusCode back to caller. |
| throw new ServiceSpecificException(vhalStatusCode); |
| } |
| |
| int propertyStatus = mapPropertyToCarPropertyStatusCode(value.prop); |
| if (value.prop == NULL_VALUE_PROP) { |
| // Return null to simulate an unavailable property. |
| // HAL implementation should return STATUS_TRY_AGAIN when a property is unavailable, |
| // however, it may also return null with STATUS_OKAY and we want to handle this |
| // properly. |
| return null; |
| } |
| VehiclePropValue currentValue = mMap.get(value.prop); |
| if (currentValue == null) { |
| return value; |
| } else { |
| currentValue.status = propertyStatus; |
| } |
| return currentValue; |
| } |
| |
| @Override |
| public synchronized void onPropertySubscribe(int property, float sampleRate) { |
| Log.d(TAG, "onPropertySubscribe property " |
| + property + " sampleRate " + sampleRate); |
| } |
| |
| @Override |
| public synchronized void onPropertyUnsubscribe(int property) { |
| Log.d(TAG, "onPropertyUnSubscribe property " + property); |
| } |
| } |
| |
| private static String propToString(int propertyId) { |
| return VehiclePropertyIds.toString(propertyId) + " (" + propertyId + ")"; |
| } |
| |
| private static int mapPropertyToVhalStatusCode(int propId) { |
| switch (propId) { |
| case PROP_CAUSE_STATUS_CODE_TRY_AGAIN: |
| return VehicleHalStatusCode.STATUS_TRY_AGAIN; |
| case PROP_CAUSE_STATUS_CODE_NOT_AVAILABLE: |
| return VehicleHalStatusCode.STATUS_NOT_AVAILABLE; |
| case PROP_CAUSE_STATUS_CODE_ACCESS_DENIED: |
| return VehicleHalStatusCode.STATUS_ACCESS_DENIED; |
| case PROP_CAUSE_STATUS_CODE_INVALID_ARG: |
| return VehicleHalStatusCode.STATUS_INVALID_ARG; |
| case PROP_CAUSE_STATUS_CODE_INTERNAL_ERROR: |
| return VehicleHalStatusCode.STATUS_INTERNAL_ERROR; |
| default: |
| return VehicleHalStatusCode.STATUS_OK; |
| } |
| } |
| |
| private static int mapPropertyToCarPropertyStatusCode(int propId) { |
| switch (propId) { |
| case INT_ARRAY_PROP_STATUS_ERROR: |
| case BOOLEAN_PROP_STATUS_ERROR: |
| return CarPropertyValue.STATUS_ERROR; |
| case INT_PROP_STATUS_UNAVAILABLE: |
| case FLOAT_PROP_STATUS_UNAVAILABLE: |
| return CarPropertyValue.STATUS_UNAVAILABLE; |
| default: |
| return CarPropertyValue.STATUS_AVAILABLE; |
| } |
| } |
| |
| private static class TestErrorCallback implements CarPropertyManager.CarPropertyEventCallback { |
| |
| private static final String CALLBACK_TAG = "ErrorEventTest"; |
| private boolean mReceivedErrorEventWithErrorCode = false; |
| private boolean mReceivedErrorEventWithOutErrorCode = false; |
| private int mErrorCode; |
| private final CountDownLatch mEventsCountDownLatch = new CountDownLatch(1); |
| private final CountDownLatch mRegisterCountDownLatch = new CountDownLatch(2); |
| @Override |
| public void onChangeEvent(CarPropertyValue value) { |
| Log.d(CALLBACK_TAG, "onChangeEvent: " + value); |
| mRegisterCountDownLatch.countDown(); |
| } |
| |
| @Override |
| public void onErrorEvent(int propId, int zone) { |
| mReceivedErrorEventWithOutErrorCode = true; |
| Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " zone: " + zone); |
| mEventsCountDownLatch.countDown(); |
| } |
| |
| @Override |
| public void onErrorEvent(int propId, int areaId, int errorCode) { |
| mReceivedErrorEventWithErrorCode = true; |
| mErrorCode = errorCode; |
| Log.d(CALLBACK_TAG, "onErrorEvent, propId: " + propId + " areaId: " + areaId |
| + "errorCode: " + errorCode); |
| mEventsCountDownLatch.countDown(); |
| } |
| |
| public void assertOnErrorEventCalled() throws InterruptedException { |
| if (!mEventsCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { |
| throw new IllegalStateException("Callback is not called in " |
| + CALLBACK_SHORT_TIMEOUT_MS + " ms."); |
| } |
| } |
| |
| public void assertOnErrorEventNotCalled() throws InterruptedException { |
| if (mEventsCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { |
| throw new IllegalStateException("Callback is called in " + CALLBACK_SHORT_TIMEOUT_MS |
| + " ms."); |
| } |
| } |
| |
| public void assertRegisterCompleted() throws InterruptedException { |
| if (!mRegisterCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { |
| throw new IllegalStateException("Register failed in " + CALLBACK_SHORT_TIMEOUT_MS |
| + " ms."); |
| } |
| } |
| } |
| |
| private class TestSequenceCallback implements CarPropertyManager.CarPropertyEventCallback { |
| |
| private ConcurrentHashMap<Integer, CarPropertyValue> mRecorder = new ConcurrentHashMap<>(); |
| private int mCounter = 0; |
| private final CountDownLatch mEventsCountDownLatch; |
| private final CountDownLatch mRegisterCountDownLatch = new CountDownLatch(2); |
| @Override |
| public void onChangeEvent(CarPropertyValue value) { |
| Log.e(TAG, "onChanged get a event " + value); |
| mRecorder.put(value.getPropertyId(), value); |
| mRegisterCountDownLatch.countDown(); |
| // Skip initial events |
| if (value.getTimestamp() != 0) { |
| mCounter++; |
| mEventsCountDownLatch.countDown(); |
| } |
| } |
| |
| TestSequenceCallback(int expectedTimes) { |
| mEventsCountDownLatch = new CountDownLatch(expectedTimes); |
| } |
| |
| @Override |
| public void onErrorEvent(int properId, int zone) { |
| Log.e(TAG, "TestSequenceCallback get an onErrorEvent"); |
| } |
| |
| public CarPropertyValue getLastCarPropertyValue(int propId) { |
| return mRecorder.get(propId); |
| } |
| |
| public int getEventCounter() { |
| return mCounter; |
| } |
| |
| public void assertOnChangeEventCalled() throws InterruptedException { |
| if (!mEventsCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { |
| throw new IllegalStateException("Callback is not called in " |
| + CALLBACK_SHORT_TIMEOUT_MS + " ms."); |
| } |
| } |
| |
| public void assertRegisterCompleted() throws InterruptedException { |
| if (!mRegisterCountDownLatch.await(CALLBACK_SHORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { |
| throw new IllegalStateException("Register failed in " + CALLBACK_SHORT_TIMEOUT_MS |
| + " ms."); |
| } |
| } |
| } |
| |
| } |