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