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.aidl;
18 
19 import android.annotation.Nullable;
20 import android.annotation.SuppressLint;
21 import android.app.compat.CompatChanges;
22 import android.compat.annotation.ChangeId;
23 import android.compat.annotation.EnabledSince;
24 import android.hardware.broadcastradio.AmFmRegionConfig;
25 import android.hardware.broadcastradio.Announcement;
26 import android.hardware.broadcastradio.DabTableEntry;
27 import android.hardware.broadcastradio.IdentifierType;
28 import android.hardware.broadcastradio.Metadata;
29 import android.hardware.broadcastradio.ProgramFilter;
30 import android.hardware.broadcastradio.ProgramIdentifier;
31 import android.hardware.broadcastradio.ProgramInfo;
32 import android.hardware.broadcastradio.ProgramListChunk;
33 import android.hardware.broadcastradio.Properties;
34 import android.hardware.broadcastradio.Result;
35 import android.hardware.broadcastradio.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.hardware.radio.RadioTuner;
41 import android.os.Build;
42 import android.os.ParcelableException;
43 import android.os.ServiceSpecificException;
44 import android.util.ArrayMap;
45 import android.util.ArraySet;
46 import android.util.IntArray;
47 
48 import com.android.server.utils.Slogf;
49 
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.Iterator;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Objects;
57 import java.util.Set;
58 
59 /**
60  * A utils class converting data types between AIDL broadcast radio HAL and
61  * {@link android.hardware.radio}
62  */
63 final class ConversionUtils {
64     private static final String TAG = "BcRadioAidlSrv.convert";
65 
66     /**
67      * With RADIO_U_VERSION_REQUIRED enabled, 44-bit DAB identifier
68      * {@link IdentifierType#DAB_SID_EXT} from broadcast radio HAL can be passed as
69      * {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} to {@link RadioTuner}.
70      */
71     @ChangeId
72     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
73     public static final long RADIO_U_VERSION_REQUIRED = 261770108L;
74 
ConversionUtils()75     private ConversionUtils() {
76         throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
77     }
78 
79     @SuppressLint("AndroidFrameworkRequiresPermission")
isAtLeastU(int uid)80     static boolean isAtLeastU(int uid) {
81         return CompatChanges.isChangeEnabled(RADIO_U_VERSION_REQUIRED, uid);
82     }
83 
throwOnError(RuntimeException halException, String action)84     static RuntimeException throwOnError(RuntimeException halException, String action) {
85         if (!(halException instanceof ServiceSpecificException)) {
86             return new ParcelableException(new RuntimeException(
87                     action + ": unknown error"));
88         }
89         int result = ((ServiceSpecificException) halException).errorCode;
90         switch (result) {
91             case Result.UNKNOWN_ERROR:
92                 return new ParcelableException(new RuntimeException(action
93                         + ": UNKNOWN_ERROR"));
94             case Result.INTERNAL_ERROR:
95                 return new ParcelableException(new RuntimeException(action
96                         + ": INTERNAL_ERROR"));
97             case Result.INVALID_ARGUMENTS:
98                 return new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
99             case Result.INVALID_STATE:
100                 return new IllegalStateException(action + ": INVALID_STATE");
101             case Result.NOT_SUPPORTED:
102                 return new UnsupportedOperationException(action + ": NOT_SUPPORTED");
103             case Result.TIMEOUT:
104                 return new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
105             case Result.CANCELED:
106                 return new IllegalStateException(action + ": CANCELED");
107             default:
108                 return new ParcelableException(new RuntimeException(
109                         action + ": unknown error (" + result + ")"));
110         }
111     }
112 
113     @RadioTuner.TunerResultType
halResultToTunerResult(int result)114     static int halResultToTunerResult(int result) {
115         switch (result) {
116             case Result.OK:
117                 return RadioTuner.TUNER_RESULT_OK;
118             case Result.INTERNAL_ERROR:
119                 return RadioTuner.TUNER_RESULT_INTERNAL_ERROR;
120             case Result.INVALID_ARGUMENTS:
121                 return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS;
122             case Result.INVALID_STATE:
123                 return RadioTuner.TUNER_RESULT_INVALID_STATE;
124             case Result.NOT_SUPPORTED:
125                 return RadioTuner.TUNER_RESULT_NOT_SUPPORTED;
126             case Result.TIMEOUT:
127                 return RadioTuner.TUNER_RESULT_TIMEOUT;
128             case Result.CANCELED:
129                 return RadioTuner.TUNER_RESULT_CANCELED;
130             case Result.UNKNOWN_ERROR:
131             default:
132                 return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR;
133         }
134     }
135 
vendorInfoToHalVendorKeyValues(@ullable Map<String, String> info)136     static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map<String, String> info) {
137         if (info == null) {
138             return new VendorKeyValue[]{};
139         }
140 
141         ArrayList<VendorKeyValue> list = new ArrayList<>();
142         for (Map.Entry<String, String> entry : info.entrySet()) {
143             VendorKeyValue elem = new VendorKeyValue();
144             elem.key = entry.getKey();
145             elem.value = entry.getValue();
146             if (elem.key == null || elem.value == null) {
147                 Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s",
148                         elem.key, elem.value);
149                 continue;
150             }
151             list.add(elem);
152         }
153 
154         return list.toArray(VendorKeyValue[]::new);
155     }
156 
vendorInfoFromHalVendorKeyValues(@ullable VendorKeyValue[] info)157     static Map<String, String> vendorInfoFromHalVendorKeyValues(@Nullable VendorKeyValue[] info) {
158         if (info == null) {
159             return Collections.emptyMap();
160         }
161 
162         Map<String, String> map = new ArrayMap<>();
163         for (VendorKeyValue kvp : info) {
164             if (kvp.key == null || kvp.value == null) {
165                 Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s",
166                         kvp.key, kvp.value);
167                 continue;
168             }
169             map.put(kvp.key, kvp.value);
170         }
171 
172         return map;
173     }
174 
175     @ProgramSelector.ProgramType
identifierTypeToProgramType( @rogramSelector.IdentifierType int idType)176     private static int identifierTypeToProgramType(
177             @ProgramSelector.IdentifierType int idType) {
178         switch (idType) {
179             case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
180             case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
181                 // TODO(b/69958423): verify AM/FM with frequency range
182                 return ProgramSelector.PROGRAM_TYPE_FM;
183             case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
184                 // TODO(b/69958423): verify AM/FM with frequency range
185                 return ProgramSelector.PROGRAM_TYPE_FM_HD;
186             case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
187             case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
188             case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
189             case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
190             case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
191                 return ProgramSelector.PROGRAM_TYPE_DAB;
192             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
193             case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
194                 return ProgramSelector.PROGRAM_TYPE_DRMO;
195             case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
196             case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
197                 return ProgramSelector.PROGRAM_TYPE_SXM;
198         }
199         if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
200                 && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
201             return idType;
202         }
203         return ProgramSelector.PROGRAM_TYPE_INVALID;
204     }
205 
identifierTypesToProgramTypes(int[] idTypes)206     private static int[] identifierTypesToProgramTypes(int[] idTypes) {
207         Set<Integer> programTypes = new ArraySet<>();
208 
209         for (int i = 0; i < idTypes.length; i++) {
210             int pType = identifierTypeToProgramType(idTypes[i]);
211 
212             if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
213 
214             programTypes.add(pType);
215             if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
216                 // TODO(b/69958423): verify AM/FM with region info
217                 programTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
218             }
219             if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
220                 // TODO(b/69958423): verify AM/FM with region info
221                 programTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
222             }
223         }
224 
225         int[] programTypesArray = new int[programTypes.size()];
226         int i = 0;
227         for (int programType : programTypes) {
228             programTypesArray[i++] = programType;
229         }
230         return programTypesArray;
231     }
232 
amfmConfigToBands( @ullable AmFmRegionConfig config)233     private static RadioManager.BandDescriptor[] amfmConfigToBands(
234             @Nullable AmFmRegionConfig config) {
235         if (config == null) {
236             return new RadioManager.BandDescriptor[0];
237         }
238 
239         int len = config.ranges.length;
240         List<RadioManager.BandDescriptor> bands = new ArrayList<>();
241 
242         // Just a placeholder value.
243         int region = RadioManager.REGION_ITU_1;
244 
245         for (int i = 0; i < len; i++) {
246             Utils.FrequencyBand bandType = Utils.getBand(config.ranges[i].lowerBound);
247             if (bandType == Utils.FrequencyBand.UNKNOWN) {
248                 Slogf.e(TAG, "Unknown frequency band at %d kHz", config.ranges[i].lowerBound);
249                 continue;
250             }
251             if (bandType == Utils.FrequencyBand.FM) {
252                 bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
253                         config.ranges[i].lowerBound, config.ranges[i].upperBound,
254                         config.ranges[i].spacing,
255 
256                         // TODO(b/69958777): stereo, rds, ta, af, ea
257                         /* stereo= */ true, /* rds= */ true, /* ta= */ true, /* af= */ true,
258                         /* ea= */ true
259                 ));
260             } else {  // AM
261                 bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
262                         config.ranges[i].lowerBound, config.ranges[i].upperBound,
263                         config.ranges[i].spacing,
264 
265                         // TODO(b/69958777): stereo
266                         /* stereo= */ true
267                 ));
268             }
269         }
270 
271         return bands.toArray(RadioManager.BandDescriptor[]::new);
272     }
273 
274     @Nullable
dabConfigFromHalDabTableEntries( @ullable DabTableEntry[] config)275     private static Map<String, Integer> dabConfigFromHalDabTableEntries(
276             @Nullable DabTableEntry[] config) {
277         if (config == null) {
278             return null;
279         }
280         Map<String, Integer> dabConfig = new ArrayMap<>();
281         for (int i = 0; i < config.length; i++) {
282             dabConfig.put(config[i].label, config[i].frequencyKhz);
283         }
284         return dabConfig;
285     }
286 
propertiesFromHalProperties(int id, String serviceName, Properties prop, @Nullable AmFmRegionConfig amfmConfig, @Nullable DabTableEntry[] dabConfig)287     static RadioManager.ModuleProperties propertiesFromHalProperties(int id,
288             String serviceName, Properties prop,
289             @Nullable AmFmRegionConfig amfmConfig, @Nullable DabTableEntry[] dabConfig) {
290         Objects.requireNonNull(serviceName);
291         Objects.requireNonNull(prop);
292 
293         int[] supportedProgramTypes = identifierTypesToProgramTypes(prop.supportedIdentifierTypes);
294 
295         return new RadioManager.ModuleProperties(
296                 id,
297                 serviceName,
298 
299                 // There is no Class concept in HAL AIDL.
300                 RadioManager.CLASS_AM_FM,
301 
302                 prop.maker,
303                 prop.product,
304                 prop.version,
305                 prop.serial,
306 
307                 // HAL AIDL only supports single tuner and audio source per
308                 // HAL implementation instance.
309                 /* numTuners= */ 1,
310                 /* numAudioSources= */ 1,
311                 /* isInitializationRequired= */ false,
312                 /* isCaptureSupported= */ false,
313 
314                 amfmConfigToBands(amfmConfig),
315                 /* isBgScanSupported= */ true,
316                 supportedProgramTypes,
317                 prop.supportedIdentifierTypes,
318                 dabConfigFromHalDabTableEntries(dabConfig),
319                 vendorInfoFromHalVendorKeyValues(prop.vendorInfo)
320         );
321     }
322 
identifierToHalProgramIdentifier(ProgramSelector.Identifier id)323     static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
324         ProgramIdentifier hwId = new ProgramIdentifier();
325         hwId.type = id.getType();
326         if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
327             hwId.type = IdentifierType.DAB_SID_EXT;
328         }
329         long value = id.getValue();
330         if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
331             hwId.value = (value & 0xFFFF) | ((value >>> 16) << 32);
332         } else {
333             hwId.value = value;
334         }
335         return hwId;
336     }
337 
338     @Nullable
identifierFromHalProgramIdentifier( ProgramIdentifier id)339     static ProgramSelector.Identifier identifierFromHalProgramIdentifier(
340             ProgramIdentifier id) {
341         if (id.type == IdentifierType.INVALID) {
342             return null;
343         }
344         int idType;
345         if (id.type == IdentifierType.DAB_SID_EXT) {
346             idType = ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
347         } else {
348             idType = id.type;
349         }
350         return new ProgramSelector.Identifier(idType, id.value);
351     }
352 
isVendorIdentifierType(int idType)353     private static boolean isVendorIdentifierType(int idType) {
354         return idType >= IdentifierType.VENDOR_START && idType <= IdentifierType.VENDOR_END;
355     }
356 
isValidHalProgramSelector( android.hardware.broadcastradio.ProgramSelector sel)357     private static boolean isValidHalProgramSelector(
358             android.hardware.broadcastradio.ProgramSelector sel) {
359         return sel.primaryId.type == IdentifierType.AMFM_FREQUENCY_KHZ
360                 || sel.primaryId.type == IdentifierType.RDS_PI
361                 || sel.primaryId.type == IdentifierType.HD_STATION_ID_EXT
362                 || sel.primaryId.type == IdentifierType.DAB_SID_EXT
363                 || sel.primaryId.type == IdentifierType.DRMO_SERVICE_ID
364                 || sel.primaryId.type == IdentifierType.SXM_SERVICE_ID
365                 || isVendorIdentifierType(sel.primaryId.type);
366     }
367 
368     @Nullable
programSelectorToHalProgramSelector( ProgramSelector sel)369     static android.hardware.broadcastradio.ProgramSelector programSelectorToHalProgramSelector(
370             ProgramSelector sel) {
371         android.hardware.broadcastradio.ProgramSelector hwSel =
372                 new android.hardware.broadcastradio.ProgramSelector();
373 
374         hwSel.primaryId = identifierToHalProgramIdentifier(sel.getPrimaryId());
375         ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
376         ArrayList<ProgramIdentifier> secondaryIdList = new ArrayList<>(secondaryIds.length);
377         for (int i = 0; i < secondaryIds.length; i++) {
378             secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i]));
379         }
380         hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new);
381         if (!isValidHalProgramSelector(hwSel)) {
382             return null;
383         }
384         return hwSel;
385     }
386 
isEmpty( android.hardware.broadcastradio.ProgramSelector sel)387     private static boolean isEmpty(
388             android.hardware.broadcastradio.ProgramSelector sel) {
389         return sel.primaryId.type == IdentifierType.INVALID && sel.primaryId.value == 0
390                 && sel.secondaryIds.length == 0;
391     }
392 
393     @Nullable
programSelectorFromHalProgramSelector( android.hardware.broadcastradio.ProgramSelector sel)394     static ProgramSelector programSelectorFromHalProgramSelector(
395             android.hardware.broadcastradio.ProgramSelector sel) {
396         if (isEmpty(sel) || !isValidHalProgramSelector(sel)) {
397             return null;
398         }
399 
400         List<ProgramSelector.Identifier> secondaryIdList = new ArrayList<>();
401         for (int i = 0; i < sel.secondaryIds.length; i++) {
402             if (sel.secondaryIds[i] != null) {
403                 secondaryIdList.add(identifierFromHalProgramIdentifier(sel.secondaryIds[i]));
404             }
405         }
406 
407         return new ProgramSelector(
408                 identifierTypeToProgramType(sel.primaryId.type),
409                 Objects.requireNonNull(identifierFromHalProgramIdentifier(sel.primaryId)),
410                 secondaryIdList.toArray(new ProgramSelector.Identifier[0]),
411                 /* vendorIds= */ null);
412     }
413 
radioMetadataFromHalMetadata(Metadata[] meta)414     private static RadioMetadata radioMetadataFromHalMetadata(Metadata[] meta) {
415         RadioMetadata.Builder builder = new RadioMetadata.Builder();
416 
417         for (int i = 0; i < meta.length; i++) {
418             switch (meta[i].getTag()) {
419                 case Metadata.rdsPs:
420                     builder.putString(RadioMetadata.METADATA_KEY_RDS_PS, meta[i].getRdsPs());
421                     break;
422                 case Metadata.rdsPty:
423                     builder.putInt(RadioMetadata.METADATA_KEY_RDS_PTY, meta[i].getRdsPty());
424                     break;
425                 case Metadata.rbdsPty:
426                     builder.putInt(RadioMetadata.METADATA_KEY_RBDS_PTY, meta[i].getRbdsPty());
427                     break;
428                 case Metadata.rdsRt:
429                     builder.putString(RadioMetadata.METADATA_KEY_RDS_RT, meta[i].getRdsRt());
430                     break;
431                 case Metadata.songTitle:
432                     builder.putString(RadioMetadata.METADATA_KEY_TITLE, meta[i].getSongTitle());
433                     break;
434                 case Metadata.songArtist:
435                     builder.putString(RadioMetadata.METADATA_KEY_ARTIST, meta[i].getSongArtist());
436                     break;
437                 case Metadata.songAlbum:
438                     builder.putString(RadioMetadata.METADATA_KEY_ALBUM, meta[i].getSongAlbum());
439                     break;
440                 case Metadata.stationIcon:
441                     builder.putInt(RadioMetadata.METADATA_KEY_ICON, meta[i].getStationIcon());
442                     break;
443                 case Metadata.albumArt:
444                     builder.putInt(RadioMetadata.METADATA_KEY_ART, meta[i].getAlbumArt());
445                     break;
446                 case Metadata.programName:
447                     builder.putString(RadioMetadata.METADATA_KEY_PROGRAM_NAME,
448                             meta[i].getProgramName());
449                     break;
450                 case Metadata.dabEnsembleName:
451                     builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME,
452                             meta[i].getDabEnsembleName());
453                     break;
454                 case Metadata.dabEnsembleNameShort:
455                     builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT,
456                             meta[i].getDabEnsembleNameShort());
457                     break;
458                 case Metadata.dabServiceName:
459                     builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME,
460                             meta[i].getDabServiceName());
461                     break;
462                 case Metadata.dabServiceNameShort:
463                     builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT,
464                             meta[i].getDabServiceNameShort());
465                     break;
466                 case Metadata.dabComponentName:
467                     builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME,
468                             meta[i].getDabComponentName());
469                     break;
470                 case Metadata.dabComponentNameShort:
471                     builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT,
472                             meta[i].getDabComponentNameShort());
473                     break;
474                 default:
475                     Slogf.w(TAG, "Ignored unknown metadata entry: %s", meta[i]);
476                     break;
477             }
478 
479         }
480 
481         return builder.build();
482     }
483 
isValidLogicallyTunedTo(ProgramIdentifier id)484     private static boolean isValidLogicallyTunedTo(ProgramIdentifier id) {
485         return id.type == IdentifierType.AMFM_FREQUENCY_KHZ || id.type == IdentifierType.RDS_PI
486                 || id.type == IdentifierType.HD_STATION_ID_EXT
487                 || id.type == IdentifierType.DAB_SID_EXT
488                 || id.type == IdentifierType.DRMO_SERVICE_ID
489                 || id.type == IdentifierType.SXM_SERVICE_ID
490                 || isVendorIdentifierType(id.type);
491     }
492 
isValidPhysicallyTunedTo(ProgramIdentifier id)493     private static boolean isValidPhysicallyTunedTo(ProgramIdentifier id) {
494         return id.type == IdentifierType.AMFM_FREQUENCY_KHZ
495                 || id.type == IdentifierType.DAB_FREQUENCY_KHZ
496                 || id.type == IdentifierType.DRMO_FREQUENCY_KHZ
497                 || id.type == IdentifierType.SXM_CHANNEL
498                 || isVendorIdentifierType(id.type);
499     }
500 
isValidHalProgramInfo(ProgramInfo info)501     private static boolean isValidHalProgramInfo(ProgramInfo info) {
502         return isValidHalProgramSelector(info.selector)
503                 && isValidLogicallyTunedTo(info.logicallyTunedTo)
504                 && isValidPhysicallyTunedTo(info.physicallyTunedTo);
505     }
506 
507     @Nullable
programInfoFromHalProgramInfo(ProgramInfo info)508     static RadioManager.ProgramInfo programInfoFromHalProgramInfo(ProgramInfo info) {
509         if (!isValidHalProgramInfo(info)) {
510             return null;
511         }
512         Collection<ProgramSelector.Identifier> relatedContent = new ArrayList<>();
513         if (info.relatedContent != null) {
514             for (int i = 0; i < info.relatedContent.length; i++) {
515                 ProgramSelector.Identifier relatedContentId =
516                         identifierFromHalProgramIdentifier(info.relatedContent[i]);
517                 if (relatedContentId != null) {
518                     relatedContent.add(relatedContentId);
519                 }
520             }
521         }
522 
523         return new RadioManager.ProgramInfo(
524                 Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)),
525                 identifierFromHalProgramIdentifier(info.logicallyTunedTo),
526                 identifierFromHalProgramIdentifier(info.physicallyTunedTo),
527                 relatedContent,
528                 info.infoFlags,
529                 info.signalQuality,
530                 radioMetadataFromHalMetadata(info.metadata),
531                 vendorInfoFromHalVendorKeyValues(info.vendorInfo)
532         );
533     }
534 
filterToHalProgramFilter(@ullable ProgramList.Filter filter)535     static ProgramFilter filterToHalProgramFilter(@Nullable ProgramList.Filter filter) {
536         if (filter == null) {
537             filter = new ProgramList.Filter();
538         }
539 
540         ProgramFilter hwFilter = new ProgramFilter();
541 
542         IntArray identifierTypeList = new IntArray(filter.getIdentifierTypes().size());
543         ArrayList<ProgramIdentifier> identifiersList = new ArrayList<>();
544         Iterator<Integer> typeIterator = filter.getIdentifierTypes().iterator();
545         while (typeIterator.hasNext()) {
546             identifierTypeList.add(typeIterator.next());
547         }
548         Iterator<ProgramSelector.Identifier> idIterator = filter.getIdentifiers().iterator();
549         while (idIterator.hasNext()) {
550             identifiersList.add(identifierToHalProgramIdentifier(idIterator.next()));
551         }
552 
553         hwFilter.identifierTypes = identifierTypeList.toArray();
554         hwFilter.identifiers = identifiersList.toArray(ProgramIdentifier[]::new);
555         hwFilter.includeCategories = filter.areCategoriesIncluded();
556         hwFilter.excludeModifications = filter.areModificationsExcluded();
557 
558         return hwFilter;
559     }
560 
chunkFromHalProgramListChunk(ProgramListChunk chunk)561     static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) {
562         Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length);
563         for (int i = 0; i < chunk.modified.length; i++) {
564             RadioManager.ProgramInfo modifiedInfo =
565                     programInfoFromHalProgramInfo(chunk.modified[i]);
566             if (modifiedInfo == null) {
567                 Slogf.w(TAG, "Program info %s in program list chunk is not valid",
568                         chunk.modified[i]);
569                 continue;
570             }
571             modified.add(modifiedInfo);
572         }
573         Set<ProgramSelector.Identifier> removed = new ArraySet<>();
574         if (chunk.removed != null) {
575             for (int i = 0; i < chunk.removed.length; i++) {
576                 ProgramSelector.Identifier removedId =
577                         identifierFromHalProgramIdentifier(chunk.removed[i]);
578                 if (removedId != null) {
579                     removed.add(removedId);
580                 }
581             }
582         }
583         return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
584     }
585 
isNewIdentifierInU(ProgramSelector.Identifier id)586     private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) {
587         return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
588     }
589 
programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid)590     static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid) {
591         if (isAtLeastU(uid)) {
592             return true;
593         }
594         if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
595             return false;
596         }
597         ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
598         for (int i = 0; i < secondaryIds.length; i++) {
599             if (isNewIdentifierInU(secondaryIds[i])) {
600                 return false;
601             }
602         }
603         return true;
604     }
605 
programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid)606     static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid) {
607         if (isAtLeastU(uid)) {
608             return true;
609         }
610         if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), uid)) {
611             return false;
612         }
613         if (isNewIdentifierInU(info.getLogicallyTunedTo())
614                 || isNewIdentifierInU(info.getPhysicallyTunedTo())) {
615             return false;
616         }
617         if (info.getRelatedContent() == null) {
618             return true;
619         }
620         Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator();
621         while (relatedContentIt.hasNext()) {
622             if (isNewIdentifierInU(relatedContentIt.next())) {
623                 return false;
624             }
625         }
626         return true;
627     }
628 
convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid)629     static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid) {
630         if (isAtLeastU(uid)) {
631             return chunk;
632         }
633         Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
634         Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
635         while (modifiedIterator.hasNext()) {
636             RadioManager.ProgramInfo info = modifiedIterator.next();
637             if (programInfoMeetsSdkVersionRequirement(info, uid)) {
638                 modified.add(info);
639             }
640         }
641         Set<ProgramSelector.Identifier> removed = new ArraySet<>();
642         Iterator<ProgramSelector.Identifier> removedIterator = chunk.getRemoved().iterator();
643         while (removedIterator.hasNext()) {
644             ProgramSelector.Identifier id = removedIterator.next();
645             if (!isNewIdentifierInU(id)) {
646                 removed.add(id);
647             }
648         }
649         return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
650     }
651 
announcementFromHalAnnouncement( Announcement hwAnnouncement)652     public static android.hardware.radio.Announcement announcementFromHalAnnouncement(
653             Announcement hwAnnouncement) {
654         return new android.hardware.radio.Announcement(
655                 Objects.requireNonNull(programSelectorFromHalProgramSelector(
656                         hwAnnouncement.selector), "Program selector can not be null"),
657                 hwAnnouncement.type,
658                 vendorInfoFromHalVendorKeyValues(hwAnnouncement.vendorInfo)
659         );
660     }
661 }
662