1 /*
2  * Copyright (C) 2022 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.server.broadcastradio.hal2;
18 
19 import static org.junit.Assert.assertThrows;
20 
21 import android.hardware.broadcastradio.V2_0.AmFmBandRange;
22 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
23 import android.hardware.broadcastradio.V2_0.DabTableEntry;
24 import android.hardware.broadcastradio.V2_0.IdentifierType;
25 import android.hardware.broadcastradio.V2_0.Metadata;
26 import android.hardware.broadcastradio.V2_0.MetadataKey;
27 import android.hardware.broadcastradio.V2_0.ProgramInfo;
28 import android.hardware.broadcastradio.V2_0.Properties;
29 import android.hardware.broadcastradio.V2_0.Result;
30 import android.hardware.broadcastradio.V2_0.VendorKeyValue;
31 import android.hardware.radio.Announcement;
32 import android.hardware.radio.ProgramSelector;
33 import android.hardware.radio.RadioManager;
34 import android.hardware.radio.RadioMetadata;
35 import android.util.ArrayMap;
36 
37 import com.google.common.truth.Expect;
38 
39 import org.junit.Rule;
40 import org.junit.Test;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 import java.util.Map;
46 
47 public final class ConvertTest {
48 
49     private static final int FM_LOWER_LIMIT = 87500;
50     private static final int FM_UPPER_LIMIT = 108000;
51     private static final int FM_SPACING = 200;
52     private static final int AM_LOWER_LIMIT = 540;
53     private static final int AM_UPPER_LIMIT = 1700;
54     private static final int AM_SPACING = 10;
55     private static final String DAB_ENTRY_LABEL_1 = "5A";
56     private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
57     private static final String DAB_ENTRY_LABEL_2 = "12D";
58     private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
59     private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
60     private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
61     private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
62     private static final String VENDOR_INFO_VALUE_2 = "vendorValue2";
63     private static final String TEST_SERVICE_NAME = "serviceMock";
64     private static final int TEST_ID = 1;
65     private static final String TEST_MAKER = "makerMock";
66     private static final String TEST_PRODUCT = "productMock";
67     private static final String TEST_VERSION = "versionMock";
68     private static final String TEST_SERIAL = "serialMock";
69 
70     private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
71     private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
72 
73     private static final RadioManager.ModuleProperties MODULE_PROPERTIES =
74             convertToModuleProperties();
75     private static final Announcement ANNOUNCEMENT =
76             Convert.announcementFromHal(
77                     TestUtils.makeAnnouncement(TEST_ENABLED_TYPE, TEST_ANNOUNCEMENT_FREQUENCY));
78 
79     @Rule
80     public final Expect expect = Expect.create();
81 
82     @Test
throwOnError()83     public void throwOnError() {
84         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
85                 Convert.throwOnError("tune", Result.INVALID_ARGUMENTS));
86 
87         expect.withMessage("Exception for illegeal argument").that(thrown)
88                 .hasMessageThat().contains("INVALID_ARGUMENTS");
89     }
90 
91     @Test
propertiesFromHalProperties_idsMatch()92     public void propertiesFromHalProperties_idsMatch() {
93         expect.withMessage("Properties id")
94                 .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
95     }
96 
97     @Test
propertiesFromHalProperties_serviceNamesMatch()98     public void propertiesFromHalProperties_serviceNamesMatch() {
99         expect.withMessage("Service name")
100                 .that(MODULE_PROPERTIES.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
101     }
102 
103     @Test
propertiesFromHalProperties_implementorsMatch()104     public void propertiesFromHalProperties_implementorsMatch() {
105         expect.withMessage("Implementor")
106                 .that(MODULE_PROPERTIES.getImplementor()).isEqualTo(TEST_MAKER);
107     }
108 
109 
110     @Test
propertiesFromHalProperties_productsMatch()111     public void propertiesFromHalProperties_productsMatch() {
112         expect.withMessage("Product")
113                 .that(MODULE_PROPERTIES.getProduct()).isEqualTo(TEST_PRODUCT);
114     }
115 
116     @Test
propertiesFromHalProperties_versionsMatch()117     public void propertiesFromHalProperties_versionsMatch() {
118         expect.withMessage("Version")
119                 .that(MODULE_PROPERTIES.getVersion()).isEqualTo(TEST_VERSION);
120     }
121 
122     @Test
propertiesFromHalProperties_serialsMatch()123     public void propertiesFromHalProperties_serialsMatch() {
124         expect.withMessage("Serial")
125                 .that(MODULE_PROPERTIES.getSerial()).isEqualTo(TEST_SERIAL);
126     }
127 
128     @Test
propertiesFromHalProperties_dabTableInfoMatch()129     public void propertiesFromHalProperties_dabTableInfoMatch() {
130         Map<String, Integer> dabTableExpected = Map.of(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1,
131                 DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2);
132 
133         expect.withMessage("Supported program types")
134                 .that(MODULE_PROPERTIES.getDabFrequencyTable())
135                 .containsExactlyEntriesIn(dabTableExpected);
136     }
137 
138     @Test
propertiesFromHalProperties_vendorInfoMatch()139     public void propertiesFromHalProperties_vendorInfoMatch() {
140         Map<String, String> vendorInfoExpected = Map.of(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1,
141                 VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2);
142 
143         expect.withMessage("Vendor info").that(MODULE_PROPERTIES.getVendorInfo())
144                 .containsExactlyEntriesIn(vendorInfoExpected);
145     }
146 
147     @Test
propertiesFromHalProperties_bandsMatch()148     public void propertiesFromHalProperties_bandsMatch() {
149         RadioManager.BandDescriptor[] bands = MODULE_PROPERTIES.getBands();
150 
151         expect.withMessage("Band descriptors").that(bands).hasLength(2);
152 
153         expect.withMessage("FM band frequency lower limit")
154                 .that(bands[0].getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
155         expect.withMessage("FM band frequency upper limit")
156                 .that(bands[0].getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
157         expect.withMessage("FM band frequency spacing")
158                 .that(bands[0].getSpacing()).isEqualTo(FM_SPACING);
159 
160         expect.withMessage("AM band frequency lower limit")
161                 .that(bands[1].getLowerLimit()).isEqualTo(AM_LOWER_LIMIT);
162         expect.withMessage("AM band frequency upper limit")
163                 .that(bands[1].getUpperLimit()).isEqualTo(AM_UPPER_LIMIT);
164         expect.withMessage("AM band frequency spacing")
165                 .that(bands[1].getSpacing()).isEqualTo(AM_SPACING);
166     }
167 
168     @Test
propertiesFromHalProperties_withInvalidBand()169     public void propertiesFromHalProperties_withInvalidBand() {
170         AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
171         amFmRegionConfig.ranges = new ArrayList<>(Arrays.asList(createAmFmBandRange(
172                 /* lowerBound= */ 50000, /* upperBound= */ 60000, /* spacing= */ 10),
173                 createAmFmBandRange(FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING)));
174 
175         RadioManager.ModuleProperties properties = convertToModuleProperties(amFmRegionConfig,
176                 new ArrayList<>());
177 
178         RadioManager.BandDescriptor[] bands = properties.getBands();
179         expect.withMessage("Band descriptors").that(bands).hasLength(1);
180         expect.withMessage("FM band frequency lower limit")
181                 .that(bands[0].getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
182         expect.withMessage("FM band frequency upper limit")
183                 .that(bands[0].getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
184         expect.withMessage("FM band frequency spacing")
185                 .that(bands[0].getSpacing()).isEqualTo(FM_SPACING);
186     }
187 
188     @Test
announcementFromHalAnnouncement_typesMatch()189     public void announcementFromHalAnnouncement_typesMatch() {
190         expect.withMessage("Announcement type")
191                 .that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
192     }
193 
194     @Test
announcementFromHalAnnouncement_selectorsMatch()195     public void announcementFromHalAnnouncement_selectorsMatch() {
196         ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
197                 ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_ANNOUNCEMENT_FREQUENCY);
198 
199         ProgramSelector selector = ANNOUNCEMENT.getSelector();
200 
201         expect.withMessage("Primary id of announcement selector")
202                 .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
203         expect.withMessage("Secondary ids of announcement selector")
204                 .that(selector.getSecondaryIds()).isEmpty();
205     }
206 
207     @Test
announcementFromHalAnnouncement_VendorInfoMatch()208     public void announcementFromHalAnnouncement_VendorInfoMatch() {
209         expect.withMessage("Announcement vendor info")
210                 .that(ANNOUNCEMENT.getVendorInfo()).isEmpty();
211     }
212 
213     @Test
getBands_withInvalidFrequency()214     public void getBands_withInvalidFrequency() {
215         expect.withMessage("Band for invalid frequency")
216                 .that(Utils.getBand(/* freq= */ 110000)).isEqualTo(FrequencyBand.UNKNOWN);
217     }
218 
219     @Test
vendorInfoToHal_withNull()220     public void vendorInfoToHal_withNull() {
221         expect.withMessage("Null vendor info converted to HAL")
222                 .that(Convert.vendorInfoToHal(/* info= */ null)).isEmpty();
223     }
224 
225     @Test
vendorInfoToHal_withNullValue()226     public void vendorInfoToHal_withNullValue() {
227         Map<String, String> vendorInfo = new ArrayMap<>();
228         vendorInfo.put(VENDOR_INFO_KEY_1, null);
229 
230         expect.withMessage("Vendor info with null value converted to HAL")
231                 .that(Convert.vendorInfoToHal(vendorInfo)).isEmpty();
232     }
233 
234     @Test
vendorInfoFromHalVendorKeyValues_withNullElements()235     public void vendorInfoFromHalVendorKeyValues_withNullElements() {
236         VendorKeyValue halVendorInfo = new VendorKeyValue();
237         halVendorInfo.key = null;
238         halVendorInfo.value = "VendorValue";
239         List<VendorKeyValue> halVendorInfoArray = List.of(halVendorInfo);
240 
241         expect.withMessage("Null vendor info converted from HAL")
242                 .that(Convert.vendorInfoFromHal(halVendorInfoArray)).isEmpty();
243     }
244 
245     @Test
programInfoFromHal_withMetadata()246     public void programInfoFromHal_withMetadata() {
247         int freq = 97900;
248         int signalQuality = 90;
249         String songTitle = "titleTest";
250         int albumArt = 1;
251         android.hardware.broadcastradio.V2_0.ProgramSelector halSelector =
252                 TestUtils.makeHalFmSelector(freq);
253         ArrayList<Metadata> metadata = new ArrayList<>(List.of(
254                 createIntMetadata(MetadataKey.ALBUM_ART, albumArt),
255                 createStringMetadata(MetadataKey.SONG_TITLE, songTitle),
256                 createStringMetadata(/* key= */ 1000, "valueForInvalidMetadataType")));
257         ProgramInfo halInfo = TestUtils.makeHalProgramInfo(halSelector, signalQuality,
258                 /* relatedContent= */ new ArrayList<>(), metadata);
259 
260         RadioManager.ProgramInfo info = Convert.programInfoFromHal(halInfo);
261 
262         RadioMetadata convertedMetadata = info.getMetadata();
263         expect.withMessage("Metadata converted from HAL")
264                 .that(convertedMetadata.size()).isEqualTo(2);
265         expect.withMessage("Song title")
266                 .that(convertedMetadata.getString(RadioMetadata.METADATA_KEY_TITLE))
267                 .isEqualTo(songTitle);
268         expect.withMessage("Album art")
269                 .that(convertedMetadata.getInt(RadioMetadata.METADATA_KEY_ART))
270                 .isEqualTo(albumArt);
271     }
272 
convertToModuleProperties()273     private static RadioManager.ModuleProperties convertToModuleProperties() {
274         AmFmRegionConfig amFmConfig = createAmFmRegionConfig();
275         List<DabTableEntry> dabTableEntries = Arrays.asList(
276                 createDabTableEntry(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1),
277                 createDabTableEntry(DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2));
278 
279         return convertToModuleProperties(amFmConfig, dabTableEntries);
280     }
281 
convertToModuleProperties( AmFmRegionConfig amFmConfig, List<DabTableEntry> dabTableEntries)282     private static RadioManager.ModuleProperties convertToModuleProperties(
283             AmFmRegionConfig amFmConfig, List<DabTableEntry> dabTableEntries) {
284         Properties properties = createHalProperties();
285         return Convert.propertiesFromHal(TEST_ID, TEST_SERVICE_NAME, properties,
286                 amFmConfig, dabTableEntries);
287     }
288 
createAmFmRegionConfig()289     private static AmFmRegionConfig createAmFmRegionConfig() {
290         AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
291         amFmRegionConfig.ranges = new ArrayList<>(Arrays.asList(
292                 createAmFmBandRange(FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING),
293                 createAmFmBandRange(AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING)));
294         return amFmRegionConfig;
295     }
296 
createAmFmBandRange(int lowerBound, int upperBound, int spacing)297     private static AmFmBandRange createAmFmBandRange(int lowerBound, int upperBound, int spacing) {
298         AmFmBandRange bandRange = new AmFmBandRange();
299         bandRange.lowerBound = lowerBound;
300         bandRange.upperBound = upperBound;
301         bandRange.spacing = spacing;
302         bandRange.scanSpacing = bandRange.spacing;
303         return bandRange;
304     }
305 
createDabTableEntry(String label, int value)306     private static DabTableEntry createDabTableEntry(String label, int value) {
307         DabTableEntry dabTableEntry = new DabTableEntry();
308         dabTableEntry.label = label;
309         dabTableEntry.frequency = value;
310         return dabTableEntry;
311     }
312 
createHalProperties()313     private static Properties createHalProperties() {
314         Properties halProperties = new Properties();
315         halProperties.supportedIdentifierTypes = new ArrayList<Integer>(Arrays.asList(
316                 IdentifierType.AMFM_FREQUENCY, IdentifierType.RDS_PI, IdentifierType.DAB_SID_EXT,
317                 IdentifierType.HD_STATION_ID_EXT, IdentifierType.DRMO_SERVICE_ID));
318         halProperties.maker = TEST_MAKER;
319         halProperties.product = TEST_PRODUCT;
320         halProperties.version = TEST_VERSION;
321         halProperties.serial = TEST_SERIAL;
322         halProperties.vendorInfo = new ArrayList<>(Arrays.asList(
323                 TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1),
324                 TestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2)));
325         return halProperties;
326     }
327 
createStringMetadata(int key, String value)328     private Metadata createStringMetadata(int key, String value) {
329         Metadata metadata = new Metadata();
330         metadata.key = key;
331         metadata.stringValue = value;
332         return metadata;
333     }
334 
createIntMetadata(int key, int value)335     private Metadata createIntMetadata(int key, int value) {
336         Metadata metadata = new Metadata();
337         metadata.key = key;
338         metadata.intValue = value;
339         return metadata;
340     }
341 }
342