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