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