1 /**
2  * Copyright (C) 2017 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.hardware.broadcastradio.V2_0.AmFmBandRange;
22 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
23 import android.hardware.broadcastradio.V2_0.Announcement;
24 import android.hardware.broadcastradio.V2_0.DabTableEntry;
25 import android.hardware.broadcastradio.V2_0.IdentifierType;
26 import android.hardware.broadcastradio.V2_0.Metadata;
27 import android.hardware.broadcastradio.V2_0.MetadataKey;
28 import android.hardware.broadcastradio.V2_0.ProgramFilter;
29 import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
30 import android.hardware.broadcastradio.V2_0.ProgramInfo;
31 import android.hardware.broadcastradio.V2_0.ProgramListChunk;
32 import android.hardware.broadcastradio.V2_0.Properties;
33 import android.hardware.broadcastradio.V2_0.Result;
34 import android.hardware.broadcastradio.V2_0.VendorKeyValue;
35 import android.hardware.radio.ProgramList;
36 import android.hardware.radio.ProgramSelector;
37 import android.hardware.radio.RadioManager;
38 import android.hardware.radio.RadioMetadata;
39 import android.os.ParcelableException;
40 import android.util.Slog;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collection;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Objects;
51 import java.util.Set;
52 import java.util.stream.Collectors;
53 
54 class Convert {
55     private static final String TAG = "BcRadio2Srv.convert";
56 
throwOnError(String action, int result)57     static void throwOnError(String action, int result) {
58         switch (result) {
59             case Result.OK:
60                 return;
61             case Result.UNKNOWN_ERROR:
62                 throw new ParcelableException(new RuntimeException(action + ": UNKNOWN_ERROR"));
63             case Result.INTERNAL_ERROR:
64                 throw new ParcelableException(new RuntimeException(action + ": INTERNAL_ERROR"));
65             case Result.INVALID_ARGUMENTS:
66                 throw new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
67             case Result.INVALID_STATE:
68                 throw new IllegalStateException(action + ": INVALID_STATE");
69             case Result.NOT_SUPPORTED:
70                 throw new UnsupportedOperationException(action + ": NOT_SUPPORTED");
71             case Result.TIMEOUT:
72                 throw new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
73             default:
74                 throw new ParcelableException(new RuntimeException(
75                         action + ": unknown error (" + result + ")"));
76         }
77     }
78 
79     static @NonNull ArrayList<VendorKeyValue>
vendorInfoToHal(@ullable Map<String, String> info)80     vendorInfoToHal(@Nullable Map<String, String> info) {
81         if (info == null) return new ArrayList<>();
82 
83         ArrayList<VendorKeyValue> list = new ArrayList<>();
84         for (Map.Entry<String, String> entry : info.entrySet()) {
85             VendorKeyValue elem = new VendorKeyValue();
86             elem.key = entry.getKey();
87             elem.value = entry.getValue();
88             if (elem.key == null || elem.value == null) {
89                 Slog.w(TAG, "VendorKeyValue contains null pointers");
90                 continue;
91             }
92             list.add(elem);
93         }
94 
95         return list;
96     }
97 
98     static @NonNull Map<String, String>
vendorInfoFromHal(@ullable List<VendorKeyValue> info)99     vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
100         if (info == null) return Collections.emptyMap();
101 
102         Map<String, String> map = new HashMap<>();
103         for (VendorKeyValue kvp : info) {
104             if (kvp.key == null || kvp.value == null) {
105                 Slog.w(TAG, "VendorKeyValue contains null pointers");
106                 continue;
107             }
108             map.put(kvp.key, kvp.value);
109         }
110 
111         return map;
112     }
113 
identifierTypeToProgramType( @rogramSelector.IdentifierType int idType)114     private static @ProgramSelector.ProgramType int identifierTypeToProgramType(
115             @ProgramSelector.IdentifierType int idType) {
116         switch (idType) {
117             case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
118             case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
119                 // TODO(b/69958423): verify AM/FM with frequency range
120                 return ProgramSelector.PROGRAM_TYPE_FM;
121             case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
122                 // TODO(b/69958423): verify AM/FM with frequency range
123                 return ProgramSelector.PROGRAM_TYPE_FM_HD;
124             case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
125             case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
126             case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
127             case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
128                 return ProgramSelector.PROGRAM_TYPE_DAB;
129             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
130             case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
131                 return ProgramSelector.PROGRAM_TYPE_DRMO;
132             case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
133             case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
134                 return ProgramSelector.PROGRAM_TYPE_SXM;
135         }
136         if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
137                 && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
138             return idType;
139         }
140         return ProgramSelector.PROGRAM_TYPE_INVALID;
141     }
142 
143     private static @NonNull int[]
identifierTypesToProgramTypes(@onNull int[] idTypes)144     identifierTypesToProgramTypes(@NonNull int[] idTypes) {
145         Set<Integer> pTypes = new HashSet<>();
146 
147         for (int idType : idTypes) {
148             int pType = identifierTypeToProgramType(idType);
149 
150             if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
151 
152             pTypes.add(pType);
153             if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
154                 // TODO(b/69958423): verify AM/FM with region info
155                 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
156             }
157             if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
158                 // TODO(b/69958423): verify AM/FM with region info
159                 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
160             }
161         }
162 
163         return pTypes.stream().mapToInt(Integer::intValue).toArray();
164     }
165 
166     private static @NonNull RadioManager.BandDescriptor[]
amfmConfigToBands(@ullable AmFmRegionConfig config)167     amfmConfigToBands(@Nullable AmFmRegionConfig config) {
168         if (config == null) return new RadioManager.BandDescriptor[0];
169 
170         int len = config.ranges.size();
171         List<RadioManager.BandDescriptor> bands = new ArrayList<>(len);
172 
173         // Just a dummy value.
174         int region = RadioManager.REGION_ITU_1;
175 
176         for (AmFmBandRange range : config.ranges) {
177             FrequencyBand bandType = Utils.getBand(range.lowerBound);
178             if (bandType == FrequencyBand.UNKNOWN) {
179                 Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
180                 continue;
181             }
182             if (bandType == FrequencyBand.FM) {
183                 bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
184                     range.lowerBound, range.upperBound, range.spacing,
185 
186                     // TODO(b/69958777): stereo, rds, ta, af, ea
187                     true, true, true, true, true
188                 ));
189             } else {  // AM
190                 bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
191                     range.lowerBound, range.upperBound, range.spacing,
192 
193                     // TODO(b/69958777): stereo
194                     true
195                 ));
196             }
197         }
198 
199         return bands.toArray(new RadioManager.BandDescriptor[bands.size()]);
200     }
201 
dabConfigFromHal( @ullable List<DabTableEntry> config)202     private static @Nullable Map<String, Integer> dabConfigFromHal(
203             @Nullable List<DabTableEntry> config) {
204         if (config == null) return null;
205         return config.stream().collect(Collectors.toMap(e -> e.label, e -> e.frequency));
206     }
207 
208     static @NonNull RadioManager.ModuleProperties
propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop, @Nullable AmFmRegionConfig amfmConfig, @Nullable List<DabTableEntry> dabConfig)209     propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop,
210             @Nullable AmFmRegionConfig amfmConfig, @Nullable List<DabTableEntry> dabConfig) {
211         Objects.requireNonNull(serviceName);
212         Objects.requireNonNull(prop);
213 
214         int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream().
215                 mapToInt(Integer::intValue).toArray();
216         int[] supportedProgramTypes = identifierTypesToProgramTypes(supportedIdentifierTypes);
217 
218         return new RadioManager.ModuleProperties(
219                 id,
220                 serviceName,
221 
222                 // There is no Class concept in HAL 2.0.
223                 RadioManager.CLASS_AM_FM,
224 
225                 prop.maker,
226                 prop.product,
227                 prop.version,
228                 prop.serial,
229 
230                 /* HAL 2.0 only supports single tuner and audio source per
231                  * HAL implementation instance. */
232                 1,      // numTuners
233                 1,      // numAudioSources
234                 false,  // isInitializationRequired
235                 false,  // isCaptureSupported
236 
237                 amfmConfigToBands(amfmConfig),
238                 true,  // isBgScanSupported is deprecated
239                 supportedProgramTypes,
240                 supportedIdentifierTypes,
241                 dabConfigFromHal(dabConfig),
242                 vendorInfoFromHal(prop.vendorInfo)
243         );
244     }
245 
programIdentifierToHal(@onNull ProgramIdentifier hwId, @NonNull ProgramSelector.Identifier id)246     static void programIdentifierToHal(@NonNull ProgramIdentifier hwId,
247             @NonNull ProgramSelector.Identifier id) {
248         hwId.type = id.getType();
249         hwId.value = id.getValue();
250     }
251 
programIdentifierToHal( @onNull ProgramSelector.Identifier id)252     static @NonNull ProgramIdentifier programIdentifierToHal(
253             @NonNull ProgramSelector.Identifier id) {
254         ProgramIdentifier hwId = new ProgramIdentifier();
255         programIdentifierToHal(hwId, id);
256         return hwId;
257     }
258 
programIdentifierFromHal( @onNull ProgramIdentifier id)259     static @Nullable ProgramSelector.Identifier programIdentifierFromHal(
260             @NonNull ProgramIdentifier id) {
261         if (id.type == IdentifierType.INVALID) return null;
262         return new ProgramSelector.Identifier(id.type, id.value);
263     }
264 
programSelectorToHal( @onNull ProgramSelector sel)265     static @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector programSelectorToHal(
266             @NonNull ProgramSelector sel) {
267         android.hardware.broadcastradio.V2_0.ProgramSelector hwSel =
268             new android.hardware.broadcastradio.V2_0.ProgramSelector();
269 
270         programIdentifierToHal(hwSel.primaryId, sel.getPrimaryId());
271         Arrays.stream(sel.getSecondaryIds()).map(Convert::programIdentifierToHal).
272                 forEachOrdered(hwSel.secondaryIds::add);
273 
274         return hwSel;
275     }
276 
isEmpty( @onNull android.hardware.broadcastradio.V2_0.ProgramSelector sel)277     private static boolean isEmpty(
278             @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
279         if (sel.primaryId.type != 0) return false;
280         if (sel.primaryId.value != 0) return false;
281         if (sel.secondaryIds.size() != 0) return false;
282         return true;
283     }
284 
programSelectorFromHal( @onNull android.hardware.broadcastradio.V2_0.ProgramSelector sel)285     static @Nullable ProgramSelector programSelectorFromHal(
286             @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
287         if (isEmpty(sel)) return null;
288 
289         ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().
290                 map(Convert::programIdentifierFromHal).map(Objects::requireNonNull).
291                 toArray(ProgramSelector.Identifier[]::new);
292 
293         return new ProgramSelector(
294                 identifierTypeToProgramType(sel.primaryId.type),
295                 Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)),
296                 secondaryIds, null);
297     }
298 
299     private enum MetadataType {
300         INT, STRING
301     }
302 
303     private static class MetadataDef {
304         private MetadataType type;
305         private String key;
MetadataDef(MetadataType type, String key)306         private MetadataDef(MetadataType type, String key) {
307             this.type = type;
308             this.key = key;
309         }
310     }
311 
312     private static final Map<Integer, MetadataDef> metadataKeys;
313     static {
314         metadataKeys = new HashMap<>();
metadataKeys.put(MetadataKey.RDS_PS, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS))315         metadataKeys.put(MetadataKey.RDS_PS, new MetadataDef(
316                 MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS));
metadataKeys.put(MetadataKey.RDS_PTY, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY))317         metadataKeys.put(MetadataKey.RDS_PTY, new MetadataDef(
318                 MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY));
metadataKeys.put(MetadataKey.RBDS_PTY, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY))319         metadataKeys.put(MetadataKey.RBDS_PTY, new MetadataDef(
320                 MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY));
metadataKeys.put(MetadataKey.RDS_RT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT))321         metadataKeys.put(MetadataKey.RDS_RT, new MetadataDef(
322                 MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT));
metadataKeys.put(MetadataKey.SONG_TITLE, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE))323         metadataKeys.put(MetadataKey.SONG_TITLE, new MetadataDef(
324                 MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE));
metadataKeys.put(MetadataKey.SONG_ARTIST, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST))325         metadataKeys.put(MetadataKey.SONG_ARTIST, new MetadataDef(
326                 MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST));
metadataKeys.put(MetadataKey.SONG_ALBUM, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM))327         metadataKeys.put(MetadataKey.SONG_ALBUM, new MetadataDef(
328                 MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM));
metadataKeys.put(MetadataKey.STATION_ICON, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_ICON))329         metadataKeys.put(MetadataKey.STATION_ICON, new MetadataDef(
330                 MetadataType.INT, RadioMetadata.METADATA_KEY_ICON));
metadataKeys.put(MetadataKey.ALBUM_ART, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_ART))331         metadataKeys.put(MetadataKey.ALBUM_ART, new MetadataDef(
332                 MetadataType.INT, RadioMetadata.METADATA_KEY_ART));
metadataKeys.put(MetadataKey.PROGRAM_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME))333         metadataKeys.put(MetadataKey.PROGRAM_NAME, new MetadataDef(
334                 MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME));
metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME))335         metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef(
336                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME));
metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT))337         metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef(
338                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT));
metadataKeys.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME))339         metadataKeys.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef(
340                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME));
metadataKeys.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT))341         metadataKeys.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef(
342                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT));
metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME))343         metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef(
344                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME));
metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT))345         metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef(
346                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT));
347     }
348 
metadataFromHal(@onNull ArrayList<Metadata> meta)349     private static @NonNull RadioMetadata metadataFromHal(@NonNull ArrayList<Metadata> meta) {
350         RadioMetadata.Builder builder = new RadioMetadata.Builder();
351 
352         for (Metadata entry : meta) {
353             MetadataDef keyDef = metadataKeys.get(entry.key);
354             if (keyDef == null) {
355                 Slog.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key));
356                 continue;
357             }
358             if (keyDef.type == MetadataType.STRING) {
359                 builder.putString(keyDef.key, entry.stringValue);
360             } else {  // MetadataType.INT
361                 /* Current java API use 32-bit values for int metadata,
362                  * but we might change it in the future */
363                 builder.putInt(keyDef.key, (int)entry.intValue);
364             }
365         }
366 
367         return builder.build();
368     }
369 
programInfoFromHal(@onNull ProgramInfo info)370     static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) {
371         Collection<ProgramSelector.Identifier> relatedContent = info.relatedContent.stream().
372                 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
373                 collect(Collectors.toList());
374 
375         return new RadioManager.ProgramInfo(
376                 Objects.requireNonNull(programSelectorFromHal(info.selector)),
377                 programIdentifierFromHal(info.logicallyTunedTo),
378                 programIdentifierFromHal(info.physicallyTunedTo),
379                 relatedContent,
380                 info.infoFlags,
381                 info.signalQuality,
382                 metadataFromHal(info.metadata),
383                 vendorInfoFromHal(info.vendorInfo)
384         );
385     }
386 
programFilterToHal(@ullable ProgramList.Filter filter)387     static @NonNull ProgramFilter programFilterToHal(@Nullable ProgramList.Filter filter) {
388         if (filter == null) filter = new ProgramList.Filter();
389 
390         ProgramFilter hwFilter = new ProgramFilter();
391 
392         filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add);
393         filter.getIdentifiers().stream().forEachOrdered(
394             id -> hwFilter.identifiers.add(programIdentifierToHal(id)));
395         hwFilter.includeCategories = filter.areCategoriesIncluded();
396         hwFilter.excludeModifications = filter.areModificationsExcluded();
397 
398         return hwFilter;
399     }
400 
programListChunkFromHal(@onNull ProgramListChunk chunk)401     static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
402         Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().
403                 map(info -> programInfoFromHal(info)).collect(Collectors.toSet());
404         Set<ProgramSelector.Identifier> removed = chunk.removed.stream().
405                 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
406                 collect(Collectors.toSet());
407 
408         return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
409     }
410 
announcementFromHal( @onNull Announcement hwAnnouncement)411     public static @NonNull android.hardware.radio.Announcement announcementFromHal(
412             @NonNull Announcement hwAnnouncement) {
413         return new android.hardware.radio.Announcement(
414             Objects.requireNonNull(programSelectorFromHal(hwAnnouncement.selector)),
415             hwAnnouncement.type,
416             vendorInfoFromHal(hwAnnouncement.vendorInfo)
417         );
418     }
419 
listToArrayList(@ullable List<T> list)420     static <T> @Nullable ArrayList<T> listToArrayList(@Nullable List<T> list) {
421         if (list == null) return null;
422         if (list instanceof ArrayList) return (ArrayList) list;
423         return new ArrayList<>(list);
424     }
425 }
426