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.DabTableEntry; 27 import android.hardware.broadcastradio.IdentifierType; 28 import android.hardware.broadcastradio.Metadata; 29 import android.hardware.broadcastradio.ProgramFilter; 30 import android.hardware.broadcastradio.ProgramIdentifier; 31 import android.hardware.broadcastradio.ProgramInfo; 32 import android.hardware.broadcastradio.ProgramListChunk; 33 import android.hardware.broadcastradio.Properties; 34 import android.hardware.broadcastradio.Result; 35 import android.hardware.broadcastradio.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.hardware.radio.RadioTuner; 41 import android.os.Build; 42 import android.os.ParcelableException; 43 import android.os.ServiceSpecificException; 44 import android.util.ArrayMap; 45 import android.util.ArraySet; 46 import android.util.IntArray; 47 48 import com.android.server.utils.Slogf; 49 50 import java.util.ArrayList; 51 import java.util.Collection; 52 import java.util.Collections; 53 import java.util.Iterator; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Objects; 57 import java.util.Set; 58 59 /** 60 * A utils class converting data types between AIDL broadcast radio HAL and 61 * {@link android.hardware.radio} 62 */ 63 final class ConversionUtils { 64 private static final String TAG = "BcRadioAidlSrv.convert"; 65 66 /** 67 * With RADIO_U_VERSION_REQUIRED enabled, 44-bit DAB identifier 68 * {@link IdentifierType#DAB_SID_EXT} from broadcast radio HAL can be passed as 69 * {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} to {@link RadioTuner}. 70 */ 71 @ChangeId 72 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 73 public static final long RADIO_U_VERSION_REQUIRED = 261770108L; 74 ConversionUtils()75 private ConversionUtils() { 76 throw new UnsupportedOperationException("ConversionUtils class is noninstantiable"); 77 } 78 79 @SuppressLint("AndroidFrameworkRequiresPermission") isAtLeastU(int uid)80 static boolean isAtLeastU(int uid) { 81 return CompatChanges.isChangeEnabled(RADIO_U_VERSION_REQUIRED, uid); 82 } 83 throwOnError(RuntimeException halException, String action)84 static RuntimeException throwOnError(RuntimeException halException, String action) { 85 if (!(halException instanceof ServiceSpecificException)) { 86 return new ParcelableException(new RuntimeException( 87 action + ": unknown error")); 88 } 89 int result = ((ServiceSpecificException) halException).errorCode; 90 switch (result) { 91 case Result.UNKNOWN_ERROR: 92 return new ParcelableException(new RuntimeException(action 93 + ": UNKNOWN_ERROR")); 94 case Result.INTERNAL_ERROR: 95 return new ParcelableException(new RuntimeException(action 96 + ": INTERNAL_ERROR")); 97 case Result.INVALID_ARGUMENTS: 98 return new IllegalArgumentException(action + ": INVALID_ARGUMENTS"); 99 case Result.INVALID_STATE: 100 return new IllegalStateException(action + ": INVALID_STATE"); 101 case Result.NOT_SUPPORTED: 102 return new UnsupportedOperationException(action + ": NOT_SUPPORTED"); 103 case Result.TIMEOUT: 104 return new ParcelableException(new RuntimeException(action + ": TIMEOUT")); 105 case Result.CANCELED: 106 return new IllegalStateException(action + ": CANCELED"); 107 default: 108 return new ParcelableException(new RuntimeException( 109 action + ": unknown error (" + result + ")")); 110 } 111 } 112 113 @RadioTuner.TunerResultType halResultToTunerResult(int result)114 static int halResultToTunerResult(int result) { 115 switch (result) { 116 case Result.OK: 117 return RadioTuner.TUNER_RESULT_OK; 118 case Result.INTERNAL_ERROR: 119 return RadioTuner.TUNER_RESULT_INTERNAL_ERROR; 120 case Result.INVALID_ARGUMENTS: 121 return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS; 122 case Result.INVALID_STATE: 123 return RadioTuner.TUNER_RESULT_INVALID_STATE; 124 case Result.NOT_SUPPORTED: 125 return RadioTuner.TUNER_RESULT_NOT_SUPPORTED; 126 case Result.TIMEOUT: 127 return RadioTuner.TUNER_RESULT_TIMEOUT; 128 case Result.CANCELED: 129 return RadioTuner.TUNER_RESULT_CANCELED; 130 case Result.UNKNOWN_ERROR: 131 default: 132 return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR; 133 } 134 } 135 vendorInfoToHalVendorKeyValues(@ullable Map<String, String> info)136 static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map<String, String> info) { 137 if (info == null) { 138 return new VendorKeyValue[]{}; 139 } 140 141 ArrayList<VendorKeyValue> list = new ArrayList<>(); 142 for (Map.Entry<String, String> entry : info.entrySet()) { 143 VendorKeyValue elem = new VendorKeyValue(); 144 elem.key = entry.getKey(); 145 elem.value = entry.getValue(); 146 if (elem.key == null || elem.value == null) { 147 Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s", 148 elem.key, elem.value); 149 continue; 150 } 151 list.add(elem); 152 } 153 154 return list.toArray(VendorKeyValue[]::new); 155 } 156 vendorInfoFromHalVendorKeyValues(@ullable VendorKeyValue[] info)157 static Map<String, String> vendorInfoFromHalVendorKeyValues(@Nullable VendorKeyValue[] info) { 158 if (info == null) { 159 return Collections.emptyMap(); 160 } 161 162 Map<String, String> map = new ArrayMap<>(); 163 for (VendorKeyValue kvp : info) { 164 if (kvp.key == null || kvp.value == null) { 165 Slogf.w(TAG, "VendorKeyValue contains invalid entry: key = %s, value = %s", 166 kvp.key, kvp.value); 167 continue; 168 } 169 map.put(kvp.key, kvp.value); 170 } 171 172 return map; 173 } 174 175 @ProgramSelector.ProgramType identifierTypeToProgramType( @rogramSelector.IdentifierType int idType)176 private static int identifierTypeToProgramType( 177 @ProgramSelector.IdentifierType int idType) { 178 switch (idType) { 179 case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY: 180 case ProgramSelector.IDENTIFIER_TYPE_RDS_PI: 181 // TODO(b/69958423): verify AM/FM with frequency range 182 return ProgramSelector.PROGRAM_TYPE_FM; 183 case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT: 184 // TODO(b/69958423): verify AM/FM with frequency range 185 return ProgramSelector.PROGRAM_TYPE_FM_HD; 186 case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC: 187 case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: 188 case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: 189 case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: 190 case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT: 191 return ProgramSelector.PROGRAM_TYPE_DAB; 192 case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: 193 case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: 194 return ProgramSelector.PROGRAM_TYPE_DRMO; 195 case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID: 196 case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL: 197 return ProgramSelector.PROGRAM_TYPE_SXM; 198 } 199 if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START 200 && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) { 201 return idType; 202 } 203 return ProgramSelector.PROGRAM_TYPE_INVALID; 204 } 205 identifierTypesToProgramTypes(int[] idTypes)206 private static int[] identifierTypesToProgramTypes(int[] idTypes) { 207 Set<Integer> programTypes = new ArraySet<>(); 208 209 for (int i = 0; i < idTypes.length; i++) { 210 int pType = identifierTypeToProgramType(idTypes[i]); 211 212 if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue; 213 214 programTypes.add(pType); 215 if (pType == ProgramSelector.PROGRAM_TYPE_FM) { 216 // TODO(b/69958423): verify AM/FM with region info 217 programTypes.add(ProgramSelector.PROGRAM_TYPE_AM); 218 } 219 if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) { 220 // TODO(b/69958423): verify AM/FM with region info 221 programTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD); 222 } 223 } 224 225 int[] programTypesArray = new int[programTypes.size()]; 226 int i = 0; 227 for (int programType : programTypes) { 228 programTypesArray[i++] = programType; 229 } 230 return programTypesArray; 231 } 232 amfmConfigToBands( @ullable AmFmRegionConfig config)233 private static RadioManager.BandDescriptor[] amfmConfigToBands( 234 @Nullable AmFmRegionConfig config) { 235 if (config == null) { 236 return new RadioManager.BandDescriptor[0]; 237 } 238 239 int len = config.ranges.length; 240 List<RadioManager.BandDescriptor> bands = new ArrayList<>(); 241 242 // Just a placeholder value. 243 int region = RadioManager.REGION_ITU_1; 244 245 for (int i = 0; i < len; i++) { 246 Utils.FrequencyBand bandType = Utils.getBand(config.ranges[i].lowerBound); 247 if (bandType == Utils.FrequencyBand.UNKNOWN) { 248 Slogf.e(TAG, "Unknown frequency band at %d kHz", config.ranges[i].lowerBound); 249 continue; 250 } 251 if (bandType == Utils.FrequencyBand.FM) { 252 bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM, 253 config.ranges[i].lowerBound, config.ranges[i].upperBound, 254 config.ranges[i].spacing, 255 256 // TODO(b/69958777): stereo, rds, ta, af, ea 257 /* stereo= */ true, /* rds= */ true, /* ta= */ true, /* af= */ true, 258 /* ea= */ true 259 )); 260 } else { // AM 261 bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM, 262 config.ranges[i].lowerBound, config.ranges[i].upperBound, 263 config.ranges[i].spacing, 264 265 // TODO(b/69958777): stereo 266 /* stereo= */ true 267 )); 268 } 269 } 270 271 return bands.toArray(RadioManager.BandDescriptor[]::new); 272 } 273 274 @Nullable dabConfigFromHalDabTableEntries( @ullable DabTableEntry[] config)275 private static Map<String, Integer> dabConfigFromHalDabTableEntries( 276 @Nullable DabTableEntry[] config) { 277 if (config == null) { 278 return null; 279 } 280 Map<String, Integer> dabConfig = new ArrayMap<>(); 281 for (int i = 0; i < config.length; i++) { 282 dabConfig.put(config[i].label, config[i].frequencyKhz); 283 } 284 return dabConfig; 285 } 286 propertiesFromHalProperties(int id, String serviceName, Properties prop, @Nullable AmFmRegionConfig amfmConfig, @Nullable DabTableEntry[] dabConfig)287 static RadioManager.ModuleProperties propertiesFromHalProperties(int id, 288 String serviceName, Properties prop, 289 @Nullable AmFmRegionConfig amfmConfig, @Nullable DabTableEntry[] dabConfig) { 290 Objects.requireNonNull(serviceName); 291 Objects.requireNonNull(prop); 292 293 int[] supportedProgramTypes = identifierTypesToProgramTypes(prop.supportedIdentifierTypes); 294 295 return new RadioManager.ModuleProperties( 296 id, 297 serviceName, 298 299 // There is no Class concept in HAL AIDL. 300 RadioManager.CLASS_AM_FM, 301 302 prop.maker, 303 prop.product, 304 prop.version, 305 prop.serial, 306 307 // HAL AIDL only supports single tuner and audio source per 308 // HAL implementation instance. 309 /* numTuners= */ 1, 310 /* numAudioSources= */ 1, 311 /* isInitializationRequired= */ false, 312 /* isCaptureSupported= */ false, 313 314 amfmConfigToBands(amfmConfig), 315 /* isBgScanSupported= */ true, 316 supportedProgramTypes, 317 prop.supportedIdentifierTypes, 318 dabConfigFromHalDabTableEntries(dabConfig), 319 vendorInfoFromHalVendorKeyValues(prop.vendorInfo) 320 ); 321 } 322 identifierToHalProgramIdentifier(ProgramSelector.Identifier id)323 static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) { 324 ProgramIdentifier hwId = new ProgramIdentifier(); 325 hwId.type = id.getType(); 326 if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) { 327 hwId.type = IdentifierType.DAB_SID_EXT; 328 } 329 long value = id.getValue(); 330 if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) { 331 hwId.value = (value & 0xFFFF) | ((value >>> 16) << 32); 332 } else { 333 hwId.value = value; 334 } 335 return hwId; 336 } 337 338 @Nullable identifierFromHalProgramIdentifier( ProgramIdentifier id)339 static ProgramSelector.Identifier identifierFromHalProgramIdentifier( 340 ProgramIdentifier id) { 341 if (id.type == IdentifierType.INVALID) { 342 return null; 343 } 344 int idType; 345 if (id.type == IdentifierType.DAB_SID_EXT) { 346 idType = ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT; 347 } else { 348 idType = id.type; 349 } 350 return new ProgramSelector.Identifier(idType, id.value); 351 } 352 isVendorIdentifierType(int idType)353 private static boolean isVendorIdentifierType(int idType) { 354 return idType >= IdentifierType.VENDOR_START && idType <= IdentifierType.VENDOR_END; 355 } 356 isValidHalProgramSelector( android.hardware.broadcastradio.ProgramSelector sel)357 private static boolean isValidHalProgramSelector( 358 android.hardware.broadcastradio.ProgramSelector sel) { 359 return sel.primaryId.type == IdentifierType.AMFM_FREQUENCY_KHZ 360 || sel.primaryId.type == IdentifierType.RDS_PI 361 || sel.primaryId.type == IdentifierType.HD_STATION_ID_EXT 362 || sel.primaryId.type == IdentifierType.DAB_SID_EXT 363 || sel.primaryId.type == IdentifierType.DRMO_SERVICE_ID 364 || sel.primaryId.type == IdentifierType.SXM_SERVICE_ID 365 || isVendorIdentifierType(sel.primaryId.type); 366 } 367 368 @Nullable programSelectorToHalProgramSelector( ProgramSelector sel)369 static android.hardware.broadcastradio.ProgramSelector programSelectorToHalProgramSelector( 370 ProgramSelector sel) { 371 android.hardware.broadcastradio.ProgramSelector hwSel = 372 new android.hardware.broadcastradio.ProgramSelector(); 373 374 hwSel.primaryId = identifierToHalProgramIdentifier(sel.getPrimaryId()); 375 ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds(); 376 ArrayList<ProgramIdentifier> secondaryIdList = new ArrayList<>(secondaryIds.length); 377 for (int i = 0; i < secondaryIds.length; i++) { 378 secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i])); 379 } 380 hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new); 381 if (!isValidHalProgramSelector(hwSel)) { 382 return null; 383 } 384 return hwSel; 385 } 386 isEmpty( android.hardware.broadcastradio.ProgramSelector sel)387 private static boolean isEmpty( 388 android.hardware.broadcastradio.ProgramSelector sel) { 389 return sel.primaryId.type == IdentifierType.INVALID && sel.primaryId.value == 0 390 && sel.secondaryIds.length == 0; 391 } 392 393 @Nullable programSelectorFromHalProgramSelector( android.hardware.broadcastradio.ProgramSelector sel)394 static ProgramSelector programSelectorFromHalProgramSelector( 395 android.hardware.broadcastradio.ProgramSelector sel) { 396 if (isEmpty(sel) || !isValidHalProgramSelector(sel)) { 397 return null; 398 } 399 400 List<ProgramSelector.Identifier> secondaryIdList = new ArrayList<>(); 401 for (int i = 0; i < sel.secondaryIds.length; i++) { 402 if (sel.secondaryIds[i] != null) { 403 secondaryIdList.add(identifierFromHalProgramIdentifier(sel.secondaryIds[i])); 404 } 405 } 406 407 return new ProgramSelector( 408 identifierTypeToProgramType(sel.primaryId.type), 409 Objects.requireNonNull(identifierFromHalProgramIdentifier(sel.primaryId)), 410 secondaryIdList.toArray(new ProgramSelector.Identifier[0]), 411 /* vendorIds= */ null); 412 } 413 radioMetadataFromHalMetadata(Metadata[] meta)414 private static RadioMetadata radioMetadataFromHalMetadata(Metadata[] meta) { 415 RadioMetadata.Builder builder = new RadioMetadata.Builder(); 416 417 for (int i = 0; i < meta.length; i++) { 418 switch (meta[i].getTag()) { 419 case Metadata.rdsPs: 420 builder.putString(RadioMetadata.METADATA_KEY_RDS_PS, meta[i].getRdsPs()); 421 break; 422 case Metadata.rdsPty: 423 builder.putInt(RadioMetadata.METADATA_KEY_RDS_PTY, meta[i].getRdsPty()); 424 break; 425 case Metadata.rbdsPty: 426 builder.putInt(RadioMetadata.METADATA_KEY_RBDS_PTY, meta[i].getRbdsPty()); 427 break; 428 case Metadata.rdsRt: 429 builder.putString(RadioMetadata.METADATA_KEY_RDS_RT, meta[i].getRdsRt()); 430 break; 431 case Metadata.songTitle: 432 builder.putString(RadioMetadata.METADATA_KEY_TITLE, meta[i].getSongTitle()); 433 break; 434 case Metadata.songArtist: 435 builder.putString(RadioMetadata.METADATA_KEY_ARTIST, meta[i].getSongArtist()); 436 break; 437 case Metadata.songAlbum: 438 builder.putString(RadioMetadata.METADATA_KEY_ALBUM, meta[i].getSongAlbum()); 439 break; 440 case Metadata.stationIcon: 441 builder.putInt(RadioMetadata.METADATA_KEY_ICON, meta[i].getStationIcon()); 442 break; 443 case Metadata.albumArt: 444 builder.putInt(RadioMetadata.METADATA_KEY_ART, meta[i].getAlbumArt()); 445 break; 446 case Metadata.programName: 447 builder.putString(RadioMetadata.METADATA_KEY_PROGRAM_NAME, 448 meta[i].getProgramName()); 449 break; 450 case Metadata.dabEnsembleName: 451 builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME, 452 meta[i].getDabEnsembleName()); 453 break; 454 case Metadata.dabEnsembleNameShort: 455 builder.putString(RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT, 456 meta[i].getDabEnsembleNameShort()); 457 break; 458 case Metadata.dabServiceName: 459 builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME, 460 meta[i].getDabServiceName()); 461 break; 462 case Metadata.dabServiceNameShort: 463 builder.putString(RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT, 464 meta[i].getDabServiceNameShort()); 465 break; 466 case Metadata.dabComponentName: 467 builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME, 468 meta[i].getDabComponentName()); 469 break; 470 case Metadata.dabComponentNameShort: 471 builder.putString(RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT, 472 meta[i].getDabComponentNameShort()); 473 break; 474 default: 475 Slogf.w(TAG, "Ignored unknown metadata entry: %s", meta[i]); 476 break; 477 } 478 479 } 480 481 return builder.build(); 482 } 483 isValidLogicallyTunedTo(ProgramIdentifier id)484 private static boolean isValidLogicallyTunedTo(ProgramIdentifier id) { 485 return id.type == IdentifierType.AMFM_FREQUENCY_KHZ || id.type == IdentifierType.RDS_PI 486 || id.type == IdentifierType.HD_STATION_ID_EXT 487 || id.type == IdentifierType.DAB_SID_EXT 488 || id.type == IdentifierType.DRMO_SERVICE_ID 489 || id.type == IdentifierType.SXM_SERVICE_ID 490 || isVendorIdentifierType(id.type); 491 } 492 isValidPhysicallyTunedTo(ProgramIdentifier id)493 private static boolean isValidPhysicallyTunedTo(ProgramIdentifier id) { 494 return id.type == IdentifierType.AMFM_FREQUENCY_KHZ 495 || id.type == IdentifierType.DAB_FREQUENCY_KHZ 496 || id.type == IdentifierType.DRMO_FREQUENCY_KHZ 497 || id.type == IdentifierType.SXM_CHANNEL 498 || isVendorIdentifierType(id.type); 499 } 500 isValidHalProgramInfo(ProgramInfo info)501 private static boolean isValidHalProgramInfo(ProgramInfo info) { 502 return isValidHalProgramSelector(info.selector) 503 && isValidLogicallyTunedTo(info.logicallyTunedTo) 504 && isValidPhysicallyTunedTo(info.physicallyTunedTo); 505 } 506 507 @Nullable programInfoFromHalProgramInfo(ProgramInfo info)508 static RadioManager.ProgramInfo programInfoFromHalProgramInfo(ProgramInfo info) { 509 if (!isValidHalProgramInfo(info)) { 510 return null; 511 } 512 Collection<ProgramSelector.Identifier> relatedContent = new ArrayList<>(); 513 if (info.relatedContent != null) { 514 for (int i = 0; i < info.relatedContent.length; i++) { 515 ProgramSelector.Identifier relatedContentId = 516 identifierFromHalProgramIdentifier(info.relatedContent[i]); 517 if (relatedContentId != null) { 518 relatedContent.add(relatedContentId); 519 } 520 } 521 } 522 523 return new RadioManager.ProgramInfo( 524 Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)), 525 identifierFromHalProgramIdentifier(info.logicallyTunedTo), 526 identifierFromHalProgramIdentifier(info.physicallyTunedTo), 527 relatedContent, 528 info.infoFlags, 529 info.signalQuality, 530 radioMetadataFromHalMetadata(info.metadata), 531 vendorInfoFromHalVendorKeyValues(info.vendorInfo) 532 ); 533 } 534 filterToHalProgramFilter(@ullable ProgramList.Filter filter)535 static ProgramFilter filterToHalProgramFilter(@Nullable ProgramList.Filter filter) { 536 if (filter == null) { 537 filter = new ProgramList.Filter(); 538 } 539 540 ProgramFilter hwFilter = new ProgramFilter(); 541 542 IntArray identifierTypeList = new IntArray(filter.getIdentifierTypes().size()); 543 ArrayList<ProgramIdentifier> identifiersList = new ArrayList<>(); 544 Iterator<Integer> typeIterator = filter.getIdentifierTypes().iterator(); 545 while (typeIterator.hasNext()) { 546 identifierTypeList.add(typeIterator.next()); 547 } 548 Iterator<ProgramSelector.Identifier> idIterator = filter.getIdentifiers().iterator(); 549 while (idIterator.hasNext()) { 550 identifiersList.add(identifierToHalProgramIdentifier(idIterator.next())); 551 } 552 553 hwFilter.identifierTypes = identifierTypeList.toArray(); 554 hwFilter.identifiers = identifiersList.toArray(ProgramIdentifier[]::new); 555 hwFilter.includeCategories = filter.areCategoriesIncluded(); 556 hwFilter.excludeModifications = filter.areModificationsExcluded(); 557 558 return hwFilter; 559 } 560 chunkFromHalProgramListChunk(ProgramListChunk chunk)561 static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) { 562 Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length); 563 for (int i = 0; i < chunk.modified.length; i++) { 564 RadioManager.ProgramInfo modifiedInfo = 565 programInfoFromHalProgramInfo(chunk.modified[i]); 566 if (modifiedInfo == null) { 567 Slogf.w(TAG, "Program info %s in program list chunk is not valid", 568 chunk.modified[i]); 569 continue; 570 } 571 modified.add(modifiedInfo); 572 } 573 Set<ProgramSelector.Identifier> removed = new ArraySet<>(); 574 if (chunk.removed != null) { 575 for (int i = 0; i < chunk.removed.length; i++) { 576 ProgramSelector.Identifier removedId = 577 identifierFromHalProgramIdentifier(chunk.removed[i]); 578 if (removedId != null) { 579 removed.add(removedId); 580 } 581 } 582 } 583 return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed); 584 } 585 isNewIdentifierInU(ProgramSelector.Identifier id)586 private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) { 587 return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT; 588 } 589 programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid)590 static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid) { 591 if (isAtLeastU(uid)) { 592 return true; 593 } 594 if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) { 595 return false; 596 } 597 ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds(); 598 for (int i = 0; i < secondaryIds.length; i++) { 599 if (isNewIdentifierInU(secondaryIds[i])) { 600 return false; 601 } 602 } 603 return true; 604 } 605 programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid)606 static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid) { 607 if (isAtLeastU(uid)) { 608 return true; 609 } 610 if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), uid)) { 611 return false; 612 } 613 if (isNewIdentifierInU(info.getLogicallyTunedTo()) 614 || isNewIdentifierInU(info.getPhysicallyTunedTo())) { 615 return false; 616 } 617 if (info.getRelatedContent() == null) { 618 return true; 619 } 620 Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator(); 621 while (relatedContentIt.hasNext()) { 622 if (isNewIdentifierInU(relatedContentIt.next())) { 623 return false; 624 } 625 } 626 return true; 627 } 628 convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid)629 static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid) { 630 if (isAtLeastU(uid)) { 631 return chunk; 632 } 633 Set<RadioManager.ProgramInfo> modified = new ArraySet<>(); 634 Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator(); 635 while (modifiedIterator.hasNext()) { 636 RadioManager.ProgramInfo info = modifiedIterator.next(); 637 if (programInfoMeetsSdkVersionRequirement(info, uid)) { 638 modified.add(info); 639 } 640 } 641 Set<ProgramSelector.Identifier> removed = new ArraySet<>(); 642 Iterator<ProgramSelector.Identifier> removedIterator = chunk.getRemoved().iterator(); 643 while (removedIterator.hasNext()) { 644 ProgramSelector.Identifier id = removedIterator.next(); 645 if (!isNewIdentifierInU(id)) { 646 removed.add(id); 647 } 648 } 649 return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed); 650 } 651 announcementFromHalAnnouncement( Announcement hwAnnouncement)652 public static android.hardware.radio.Announcement announcementFromHalAnnouncement( 653 Announcement hwAnnouncement) { 654 return new android.hardware.radio.Announcement( 655 Objects.requireNonNull(programSelectorFromHalProgramSelector( 656 hwAnnouncement.selector), "Program selector can not be null"), 657 hwAnnouncement.type, 658 vendorInfoFromHalVendorKeyValues(hwAnnouncement.vendorInfo) 659 ); 660 } 661 } 662