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