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