Add test for announcement and utils HIDL radio

Unit tests for announcement aggregator and utils classes in HIDL
2.0 broadcast radio HAL client was added.

Bug: 258034192
Test: atest com.android.server.broadcastradio.hal2
Change-Id: Ie605de2f1a1090a8861bfcd03370057ba96e9d8c
This commit is contained in:
Weilin Xu
2022-11-07 21:23:11 +00:00
parent 58e1c9fba4
commit f967e62677
6 changed files with 446 additions and 8 deletions

View File

@@ -19,7 +19,6 @@ package com.android.server.broadcastradio.aidl;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -57,11 +56,8 @@ public final class AnnouncementAggregatorTest {
private IAnnouncementListener mListenerMock;
@Mock
private IBinder mBinderMock;
// Array of mocked radio modules
private RadioModule[] mRadioModuleMocks;
// Array of mocked close handles
private ICloseHandle[] mCloseHandleMocks;
// Array of mocked announcements
private Announcement[] mAnnouncementMocks;
@Before
@@ -72,7 +68,7 @@ public final class AnnouncementAggregatorTest {
mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), anyInt());
verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), eq(0));
mDeathRecipient = deathRecipientCaptor.getValue();
}
@@ -105,7 +101,8 @@ public final class AnnouncementAggregatorTest {
moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
assertWithMessage("Number of announcements %s", announcementsCaptor.getValue())
assertWithMessage("Number of announcements %s after %s announcements were updated",
announcementsCaptor.getValue(), index + 1)
.that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
}
}
@@ -117,7 +114,7 @@ public final class AnnouncementAggregatorTest {
mAnnouncementAggregator.close();
verify(mCloseHandleMocks[0]).close();
verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
}
@Test
@@ -140,7 +137,7 @@ public final class AnnouncementAggregatorTest {
mAnnouncementAggregator.close();
verify(mCloseHandleMocks[0]).close();
verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
}
@Test

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2022 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.server.broadcastradio.hal2;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.radio.Announcement;
import android.hardware.radio.IAnnouncementListener;
import android.hardware.radio.ICloseHandle;
import android.os.IBinder;
import android.os.RemoteException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
/**
* Tests for HIDL 2.0 HAL AnnouncementAggregator.
*/
@RunWith(MockitoJUnitRunner.class)
public final class AnnouncementAggregatorHidlTest {
private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
private final Object mLock = new Object();
private AnnouncementAggregator mAnnouncementAggregator;
private IBinder.DeathRecipient mDeathRecipient;
@Mock
private IAnnouncementListener mListenerMock;
@Mock
private IBinder mBinderMock;
private RadioModule[] mRadioModuleMocks;
private ICloseHandle[] mCloseHandleMocks;
private Announcement[] mAnnouncementMocks;
@Before
public void setUp() throws Exception {
ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
when(mListenerMock.asBinder()).thenReturn(mBinderMock);
mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), eq(0));
mDeathRecipient = deathRecipientCaptor.getValue();
}
@Test
public void onListUpdated_withOneModuleWatcher() throws Exception {
ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
ArgumentCaptor.forClass(IAnnouncementListener.class);
watchModules(/* moduleNumber= */ 1);
verify(mRadioModuleMocks[0]).addAnnouncementListener(any(), moduleWatcherCaptor.capture());
moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[0]));
verify(mListenerMock).onListUpdated(any());
}
@Test
public void onListUpdated_withMultipleModuleWatchers() throws Exception {
int moduleNumber = 3;
watchModules(moduleNumber);
for (int index = 0; index < moduleNumber; index++) {
ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
ArgumentCaptor.forClass(IAnnouncementListener.class);
ArgumentCaptor<List<Announcement>> announcementsCaptor =
ArgumentCaptor.forClass(List.class);
verify(mRadioModuleMocks[index])
.addAnnouncementListener(any(), moduleWatcherCaptor.capture());
moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
assertWithMessage("Number of announcements %s after %s announcements were updated",
announcementsCaptor.getValue(), index + 1)
.that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
}
}
@Test
public void close_withOneModuleWatcher_invokesCloseHandle() throws Exception {
watchModules(/* moduleNumber= */ 1);
mAnnouncementAggregator.close();
verify(mCloseHandleMocks[0]).close();
verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
}
@Test
public void close_withMultipleModuleWatcher_invokesCloseHandles() throws Exception {
int moduleNumber = 3;
watchModules(moduleNumber);
mAnnouncementAggregator.close();
for (int index = 0; index < moduleNumber; index++) {
verify(mCloseHandleMocks[index]).close();
}
}
@Test
public void close_twice_invokesCloseHandleOnce() throws Exception {
watchModules(/* moduleNumber= */ 1);
mAnnouncementAggregator.close();
mAnnouncementAggregator.close();
verify(mCloseHandleMocks[0]).close();
verify(mBinderMock).unlinkToDeath(mDeathRecipient, 0);
}
@Test
public void binderDied_forDeathRecipient_invokesCloseHandle() throws Exception {
watchModules(/* moduleNumber= */ 1);
mDeathRecipient.binderDied();
verify(mCloseHandleMocks[0]).close();
}
private void watchModules(int moduleNumber) throws RemoteException {
mRadioModuleMocks = new RadioModule[moduleNumber];
mCloseHandleMocks = new ICloseHandle[moduleNumber];
mAnnouncementMocks = new Announcement[moduleNumber];
for (int index = 0; index < moduleNumber; index++) {
mRadioModuleMocks[index] = mock(RadioModule.class);
mCloseHandleMocks[index] = mock(ICloseHandle.class);
mAnnouncementMocks[index] = mock(Announcement.class);
when(mRadioModuleMocks[index].addAnnouncementListener(any(), any()))
.thenReturn(mCloseHandleMocks[index]);
mAnnouncementAggregator.watchModule(mRadioModuleMocks[index], TEST_ENABLED_TYPES);
}
}
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2022 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.server.broadcastradio.hal2;
import android.hardware.broadcastradio.V2_0.AmFmBandRange;
import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
import android.hardware.broadcastradio.V2_0.DabTableEntry;
import android.hardware.broadcastradio.V2_0.IdentifierType;
import android.hardware.broadcastradio.V2_0.Properties;
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.Announcement;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import com.google.common.truth.Expect;
import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public final class ConvertTest {
private static final int FM_LOWER_LIMIT = 87500;
private static final int FM_UPPER_LIMIT = 108000;
private static final int FM_SPACING = 200;
private static final int AM_LOWER_LIMIT = 540;
private static final int AM_UPPER_LIMIT = 1700;
private static final int AM_SPACING = 10;
private static final String DAB_ENTRY_LABEL_1 = "5A";
private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
private static final String DAB_ENTRY_LABEL_2 = "12D";
private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
private static final String VENDOR_INFO_VALUE_2 = "vendorValue2";
private static final String TEST_SERVICE_NAME = "serviceMock";
private static final int TEST_ID = 1;
private static final String TEST_MAKER = "makerMock";
private static final String TEST_PRODUCT = "productMock";
private static final String TEST_VERSION = "versionMock";
private static final String TEST_SERIAL = "serialMock";
private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
private static final RadioManager.ModuleProperties MODULE_PROPERTIES =
convertToModuleProperties();
private static final Announcement ANNOUNCEMENT =
Convert.announcementFromHal(
TestUtils.makeAnnouncement(TEST_ENABLED_TYPE, TEST_ANNOUNCEMENT_FREQUENCY));
@Rule
public final Expect expect = Expect.create();
@Test
public void propertiesFromHalProperties_idsMatch() {
expect.withMessage("Properties id")
.that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
}
@Test
public void propertiesFromHalProperties_serviceNamesMatch() {
expect.withMessage("Service name")
.that(MODULE_PROPERTIES.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
}
@Test
public void propertiesFromHalProperties_implementorsMatch() {
expect.withMessage("Implementor")
.that(MODULE_PROPERTIES.getImplementor()).isEqualTo(TEST_MAKER);
}
@Test
public void propertiesFromHalProperties_productsMatch() {
expect.withMessage("Product")
.that(MODULE_PROPERTIES.getProduct()).isEqualTo(TEST_PRODUCT);
}
@Test
public void propertiesFromHalProperties_versionsMatch() {
expect.withMessage("Version")
.that(MODULE_PROPERTIES.getVersion()).isEqualTo(TEST_VERSION);
}
@Test
public void propertiesFromHalProperties_serialsMatch() {
expect.withMessage("Serial")
.that(MODULE_PROPERTIES.getSerial()).isEqualTo(TEST_SERIAL);
}
@Test
public void propertiesFromHalProperties_dabTableInfoMatch() {
Map<String, Integer> dabTableExpected = Map.of(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1,
DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2);
expect.withMessage("Supported program types")
.that(MODULE_PROPERTIES.getDabFrequencyTable())
.containsExactlyEntriesIn(dabTableExpected);
}
@Test
public void propertiesFromHalProperties_vendorInfoMatch() {
Map<String, String> vendorInfoExpected = Map.of(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1,
VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2);
expect.withMessage("Vendor info").that(MODULE_PROPERTIES.getVendorInfo())
.containsExactlyEntriesIn(vendorInfoExpected);
}
@Test
public void propertiesFromHalProperties_bandsMatch() {
RadioManager.BandDescriptor[] bands = MODULE_PROPERTIES.getBands();
expect.withMessage("Band descriptors").that(bands).hasLength(2);
expect.withMessage("FM band frequency lower limit")
.that(bands[0].getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
expect.withMessage("FM band frequency upper limit")
.that(bands[0].getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
expect.withMessage("FM band frequency spacing")
.that(bands[0].getSpacing()).isEqualTo(FM_SPACING);
expect.withMessage("AM band frequency lower limit")
.that(bands[1].getLowerLimit()).isEqualTo(AM_LOWER_LIMIT);
expect.withMessage("AM band frequency upper limit")
.that(bands[1].getUpperLimit()).isEqualTo(AM_UPPER_LIMIT);
expect.withMessage("AM band frequency spacing")
.that(bands[1].getSpacing()).isEqualTo(AM_SPACING);
}
@Test
public void announcementFromHalAnnouncement_typesMatch() {
expect.withMessage("Announcement type")
.that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
}
@Test
public void announcementFromHalAnnouncement_selectorsMatch() {
ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_ANNOUNCEMENT_FREQUENCY);
ProgramSelector selector = ANNOUNCEMENT.getSelector();
expect.withMessage("Primary id of announcement selector")
.that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
expect.withMessage("Secondary ids of announcement selector")
.that(selector.getSecondaryIds()).isEmpty();
}
@Test
public void announcementFromHalAnnouncement_VendorInfoMatch() {
expect.withMessage("Announcement vendor info")
.that(ANNOUNCEMENT.getVendorInfo()).isEmpty();
}
private static RadioManager.ModuleProperties convertToModuleProperties() {
AmFmRegionConfig amFmConfig = createAmFmRegionConfig();
List<DabTableEntry> dabTableEntries = Arrays.asList(
createDabTableEntry(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1),
createDabTableEntry(DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2));
Properties properties = createHalProperties();
return Convert.propertiesFromHal(TEST_ID, TEST_SERVICE_NAME, properties,
amFmConfig, dabTableEntries);
}
private static AmFmRegionConfig createAmFmRegionConfig() {
AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
amFmRegionConfig.ranges = new ArrayList<AmFmBandRange>(Arrays.asList(
createAmFmBandRange(FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING),
createAmFmBandRange(AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING)));
return amFmRegionConfig;
}
private static AmFmBandRange createAmFmBandRange(int lowerBound, int upperBound, int spacing) {
AmFmBandRange bandRange = new AmFmBandRange();
bandRange.lowerBound = lowerBound;
bandRange.upperBound = upperBound;
bandRange.spacing = spacing;
bandRange.scanSpacing = bandRange.spacing;
return bandRange;
}
private static DabTableEntry createDabTableEntry(String label, int value) {
DabTableEntry dabTableEntry = new DabTableEntry();
dabTableEntry.label = label;
dabTableEntry.frequency = value;
return dabTableEntry;
}
private static Properties createHalProperties() {
Properties halProperties = new Properties();
halProperties.supportedIdentifierTypes = new ArrayList<Integer>(Arrays.asList(
IdentifierType.AMFM_FREQUENCY, IdentifierType.RDS_PI, IdentifierType.DAB_SID_EXT));
halProperties.maker = TEST_MAKER;
halProperties.product = TEST_PRODUCT;
halProperties.version = TEST_VERSION;
halProperties.serial = TEST_SERIAL;
halProperties.vendorInfo = new ArrayList<VendorKeyValue>(Arrays.asList(
TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1),
TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2)));
return halProperties;
}
}

View File

@@ -15,14 +15,23 @@
*/
package com.android.server.broadcastradio.hal2;
import android.hardware.broadcastradio.V2_0.IdentifierType;
import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import java.util.ArrayList;
import java.util.HashMap;
final class TestUtils {
private TestUtils() {
throw new UnsupportedOperationException("TestUtils class is noninstantiable");
}
static RadioManager.ProgramInfo makeProgramInfo(int programType,
ProgramSelector.Identifier identifier, int signalQuality) {
// Note: If you set new fields, check if programInfoToHal() needs to be updated as well.
@@ -39,4 +48,33 @@ final class TestUtils {
hwInfo.signalQuality = info.getSignalStrength();
return hwInfo;
}
static android.hardware.broadcastradio.V2_0.ProgramSelector makeHalFmSelector(int freq) {
ProgramIdentifier halId = new ProgramIdentifier();
halId.type = IdentifierType.AMFM_FREQUENCY;
halId.value = freq;
android.hardware.broadcastradio.V2_0.ProgramSelector halSelector =
new android.hardware.broadcastradio.V2_0.ProgramSelector();
halSelector.primaryId = halId;
halSelector.secondaryIds = new ArrayList<>();
return halSelector;
}
static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
VendorKeyValue vendorKeyValue = new VendorKeyValue();
vendorKeyValue.key = vendorKey;
vendorKeyValue.value = vendorValue;
return vendorKeyValue;
}
static android.hardware.broadcastradio.V2_0.Announcement makeAnnouncement(int type,
int selectorFreq) {
android.hardware.broadcastradio.V2_0.Announcement halAnnouncement =
new android.hardware.broadcastradio.V2_0.Announcement();
halAnnouncement.type = (byte) type;
halAnnouncement.selector = makeHalFmSelector(selectorFreq);
halAnnouncement.vendorInfo = new ArrayList<>();
return halAnnouncement;
}
}

View File

@@ -52,8 +52,13 @@ import java.util.Set;
import java.util.stream.Collectors;
class Convert {
private static final String TAG = "BcRadio2Srv.convert";
private Convert() {
throw new UnsupportedOperationException("Convert class is noninstantiable");
}
static void throwOnError(String action, int result) {
switch (result) {
case Result.OK:

View File

@@ -28,6 +28,11 @@ enum FrequencyBand {
};
class Utils {
private Utils() {
throw new UnsupportedOperationException("Utils class is noninstantiable");
}
private static final String TAG = "BcRadio2Srv.utils";
static FrequencyBand getBand(int freq) {