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