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