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