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 android.hardware.radio; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.IntDef; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SystemApi; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.stream.Stream; 35 36 /** 37 * A set of identifiers necessary to tune to a given station. 38 * 39 * <p>This can hold various identifiers, like 40 * <ui> 41 * <li>AM/FM frequency</li> 42 * <li>HD Radio subchannel</li> 43 * <li>DAB channel info</li> 44 * </ui> 45 * 46 * <p>Except for DAB radio, two selectors with different secondary IDs, but the same primary 47 * ID are considered equal. In particular, secondary IDs vector may get updated for 48 * an entry on the program list (ie. when a better frequency for a given 49 * station is found). For DAB radio, two selectors with the same primary ID and the same 50 * DAB frequency and DAB ensemble secondary IDs (if exist) are considered equal. 51 * 52 * <p>The primaryId of a given programType MUST be of a specific type: 53 * <ui> 54 * <li>AM, FM: {@link #IDENTIFIER_TYPE_RDS_PI} if the station broadcasts RDS, 55 * {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} otherwise;</li> 56 * <li>AM_HD, FM_HD: {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT};</li> 57 * <li>DAB: {@link #IDENTIFIER_TYPE_DAB_SID_EXT} or 58 * {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT};</li> 59 * <li>DRMO: {@link #IDENTIFIER_TYPE_DRMO_SERVICE_ID};</li> 60 * <li>VENDOR: VENDOR_PRIMARY.</li> 61 * </ui> 62 * @hide 63 */ 64 @SystemApi 65 public final class ProgramSelector implements Parcelable { 66 /** Invalid program type. 67 * @deprecated use {@link IdentifierType} instead 68 */ 69 @Deprecated 70 public static final int PROGRAM_TYPE_INVALID = 0; 71 /** Analog AM radio (with or without RDS). 72 * @deprecated use {@link IdentifierType} instead 73 */ 74 @Deprecated 75 public static final int PROGRAM_TYPE_AM = 1; 76 /** analog FM radio (with or without RDS). 77 * @deprecated use {@link IdentifierType} instead 78 */ 79 @Deprecated 80 public static final int PROGRAM_TYPE_FM = 2; 81 /** AM HD Radio. 82 * @deprecated use {@link Identifier} instead 83 */ 84 @Deprecated 85 public static final int PROGRAM_TYPE_AM_HD = 3; 86 /** FM HD Radio. 87 * @deprecated use {@link Identifier} instead 88 */ 89 @Deprecated 90 public static final int PROGRAM_TYPE_FM_HD = 4; 91 /** Digital audio broadcasting. 92 * @deprecated use {@link Identifier} instead 93 */ 94 @Deprecated 95 public static final int PROGRAM_TYPE_DAB = 5; 96 /** Digital Radio Mondiale. 97 * @deprecated use {@link Identifier} instead 98 */ 99 @Deprecated 100 public static final int PROGRAM_TYPE_DRMO = 6; 101 /** SiriusXM Satellite Radio. 102 * @deprecated use {@link Identifier} instead 103 */ 104 @Deprecated 105 public static final int PROGRAM_TYPE_SXM = 7; 106 /** Vendor-specific, not synced across devices. 107 * @deprecated use {@link Identifier} instead 108 */ 109 @Deprecated 110 public static final int PROGRAM_TYPE_VENDOR_START = 1000; 111 /** @deprecated use {@link Identifier} instead */ 112 @Deprecated 113 public static final int PROGRAM_TYPE_VENDOR_END = 1999; 114 /** 115 * @deprecated use {@link Identifier} instead 116 * @removed mistakenly exposed previously 117 */ 118 @Deprecated 119 @IntDef(prefix = { "PROGRAM_TYPE_" }, value = { 120 PROGRAM_TYPE_INVALID, 121 PROGRAM_TYPE_AM, 122 PROGRAM_TYPE_FM, 123 PROGRAM_TYPE_AM_HD, 124 PROGRAM_TYPE_FM_HD, 125 PROGRAM_TYPE_DAB, 126 PROGRAM_TYPE_DRMO, 127 PROGRAM_TYPE_SXM, 128 }) 129 @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END) 130 @Retention(RetentionPolicy.SOURCE) 131 public @interface ProgramType {} 132 133 /** 134 * Bitmask for HD radio subchannel 1 135 * 136 * <p>There are at most 8 HD radio subchannels of 1-based om HD radio standard. It is 137 * converted to 0-based index. 0 is the index of main program service (MPS). 1 to 7 are 138 * indexes of additional supplemental program services (SPS). 139 */ 140 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 141 public static final int SUB_CHANNEL_HD_1 = 1 << 0; 142 143 /** 144 * Bitmask for HD radio subchannel 2 145 * 146 * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} 147 */ 148 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 149 public static final int SUB_CHANNEL_HD_2 = 1 << 1; 150 151 /** 152 * Bitmask for HD radio subchannel 3 153 * 154 * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} 155 */ 156 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 157 public static final int SUB_CHANNEL_HD_3 = 1 << 2; 158 159 /** 160 * Bitmask for HD radio subchannel 4 161 * 162 * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} 163 */ 164 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 165 public static final int SUB_CHANNEL_HD_4 = 1 << 3; 166 167 /** 168 * Bitmask for HD radio subchannel 5 169 * 170 * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} 171 */ 172 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 173 public static final int SUB_CHANNEL_HD_5 = 1 << 4; 174 175 /** 176 * Bitmask for HD radio subchannel 6 177 * 178 * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} 179 */ 180 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 181 public static final int SUB_CHANNEL_HD_6 = 1 << 5; 182 183 /** 184 * Bitmask for HD radio subchannel 7 185 * 186 * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} 187 */ 188 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 189 public static final int SUB_CHANNEL_HD_7 = 1 << 6; 190 191 /** 192 * Bitmask for HD radio subchannel 8 193 * 194 * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} 195 */ 196 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 197 public static final int SUB_CHANNEL_HD_8 = 1 << 7; 198 199 /** @hide */ 200 @IntDef(prefix = { "SUB_CHANNEL_HD_" }, value = { 201 SUB_CHANNEL_HD_1, 202 SUB_CHANNEL_HD_2, 203 SUB_CHANNEL_HD_3, 204 SUB_CHANNEL_HD_4, 205 SUB_CHANNEL_HD_5, 206 SUB_CHANNEL_HD_6, 207 SUB_CHANNEL_HD_7, 208 SUB_CHANNEL_HD_8, 209 }) 210 @Retention(RetentionPolicy.SOURCE) 211 public @interface HdSubChannel {} 212 213 public static final int IDENTIFIER_TYPE_INVALID = 0; 214 /** 215 * Primary identifier for analog (without RDS) AM/FM stations: 216 * frequency in kHz. 217 * 218 * <p>This identifier also contains band information: 219 * <li> 220 * <ul><500kHz: AM LW. 221 * <ul>500kHz - 1705kHz: AM MW. 222 * <ul>1.71MHz - 30MHz: AM SW. 223 * <ul>>60MHz: FM. 224 * </li> 225 */ 226 public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; 227 /** 228 * 16bit primary identifier for FM RDS station. 229 */ 230 public static final int IDENTIFIER_TYPE_RDS_PI = 2; 231 /** 232 * 64bit compound primary identifier for HD Radio. 233 * 234 * <p>Consists of (from the LSB): 235 * <li> 236 * <ul>32bit: Station ID number.</ul> 237 * <ul>4bit: HD subchannel, see {@link #SUB_CHANNEL_HD_1}.</ul> 238 * <ul>18bit: AMFM_FREQUENCY.</ul> 239 * </li> 240 * 241 * <p>While station ID number should be unique globally, it sometimes gets 242 * abused by broadcasters (i.e. not being set at all). To ensure local 243 * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is 244 * a best-effort - see {@link #IDENTIFIER_TYPE_HD_STATION_NAME}. 245 * 246 * <p>The remaining bits should be set to zeros when writing on the chip side 247 * and ignored when read. 248 */ 249 public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; 250 /** 251 * HD Radio subchannel - a value in range of 0-7. 252 * 253 * <p>The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS), 254 * as opposed to HD Radio standard (where it's 1-based). 255 * 256 * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead 257 */ 258 @Deprecated 259 public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; 260 /** 261 * 64bit additional identifier for HD Radio. 262 * 263 * <p>Due to Station ID abuse, some {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT} 264 * identifiers may be not globally unique. To provide a best-effort solution, a 265 * short version of station name may be carried as additional identifier and 266 * may be used by the tuner hardware to double-check tuning. 267 * 268 * <p>The name is limited to the first 8 A-Z0-9 characters (lowercase 269 * letters must be converted to uppercase). Encoded in little-endian 270 * ASCII: the first character of the name is the LSB. 271 * 272 * <p>For example: "Abc" is encoded as 0x434241. 273 */ 274 public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; 275 /** 276 * @see #IDENTIFIER_TYPE_DAB_SID_EXT 277 * 278 * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead 279 */ 280 @Deprecated 281 public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; 282 /** 283 * 28bit compound primary identifier for Digital Audio Broadcasting. 284 * 285 * <p>Consists of (from the LSB): 286 * <li> 287 * <ul>16bit: SId.</ul> 288 * <ul>8bit: ECC code.</ul> 289 * <ul>4bit: SCIdS.</ul> 290 * </li> 291 * 292 * <p>SCIdS (Service Component Identifier within the Service) value 293 * of 0 represents the main service, while 1 and above represents 294 * secondary services. 295 * 296 * <p>The remaining bits should be set to zeros when writing on the chip 297 * side and ignored when read. 298 * 299 * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead 300 */ 301 @Deprecated 302 public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC; 303 /** 16bit */ 304 public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; 305 /** 12bit */ 306 public static final int IDENTIFIER_TYPE_DAB_SCID = 7; 307 /** kHz */ 308 public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; 309 /** 310 * 24bit primary identifier for Digital Radio Mondiale. 311 */ 312 public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; 313 /** kHz */ 314 public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; 315 /** 316 * 1: AM, 2:FM 317 * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead 318 */ 319 @Deprecated 320 public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; 321 /** 322 * 32bit primary identifier for SiriusXM Satellite Radio. 323 * 324 * @deprecated SiriusXM Satellite Radio is not supported 325 */ 326 @Deprecated 327 public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; 328 /** 329 * 0-999 range 330 * 331 * @deprecated SiriusXM Satellite Radio is not supported 332 */ 333 @Deprecated 334 public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; 335 /** 336 * 44bit compound primary identifier for Digital Audio Broadcasting and 337 * Digital Multimedia Broadcasting. 338 * 339 * <p>Consists of (from the LSB): 340 * <li> 341 * <ul>32bit: SId;</ul> 342 * <ul>8bit: ECC code;</ul> 343 * <ul>4bit: SCIdS.</ul> 344 * </li> 345 * 346 * <p>SCIdS (Service Component Identifier within the Service) value 347 * of 0 represents the main service, while 1 and above represents 348 * secondary services. 349 * 350 * <p>The remaining bits should be set to zeros when writing on the chip 351 * side and ignored when read. 352 */ 353 public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; 354 /** 355 * 64bit additional identifier for HD Radio representing station location. 356 * 357 * <p>Consists of (from the LSB): 358 * <li> 359 * <ul>4 bit: Bits 0:3 of altitude</ul> 360 * <ul>13 bit: Fractional bits of longitude</ul> 361 * <ul>8 bit: Integer bits of longitude</ul> 362 * <ul>1 bit: 0 for east and 1 for west for longitude</ul> 363 * <ul>1 bit: 0, representing longitude</ul> 364 * <ul>5 bit: pad of zeros separating longitude and latitude</ul> 365 * <ul>4 bit: Bits 4:7 of altitude</ul> 366 * <ul>13 bit: Fractional bits of latitude</ul> 367 * <ul>8 bit: Integer bits of latitude</ul> 368 * <ul>1 bit: 0 for north and 1 for south for latitude</ul> 369 * <ul>1 bit: 1, representing latitude</ul> 370 * <ul>5 bit: pad of zeros</ul> 371 * </li> 372 * 373 * <p>This format is defined in NRSC-5-C document: SY_IDD_1020s. 374 * 375 * <p>Due to Station ID abuse, some 376 * {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT} identifiers may be not 377 * globally unique. To provide a best-effort solution, the station’s 378 * broadcast antenna containing the latitude and longitude may be 379 * carried as additional identifier and may be used by the tuner hardware 380 * to double-check tuning. 381 */ 382 @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) 383 public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; 384 /** 385 * Primary identifier for vendor-specific radio technology. 386 * The value format is determined by a vendor. 387 * 388 * <p>It must not be used in any other programType than corresponding VENDOR 389 * type between VENDOR_START and VENDOR_END (e.g. identifier type 1015 must 390 * not be used in any program type other than 1015). 391 */ 392 public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START; 393 /** 394 * @see #IDENTIFIER_TYPE_VENDOR_START 395 */ 396 public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END; 397 /** 398 * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_START} instead 399 */ 400 @Deprecated 401 public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START; 402 /** 403 * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_END} instead 404 */ 405 @Deprecated 406 public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END; 407 /** @removed mistakenly exposed previously */ 408 @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = { 409 IDENTIFIER_TYPE_INVALID, 410 IDENTIFIER_TYPE_AMFM_FREQUENCY, 411 IDENTIFIER_TYPE_RDS_PI, 412 IDENTIFIER_TYPE_HD_STATION_ID_EXT, 413 IDENTIFIER_TYPE_HD_SUBCHANNEL, 414 IDENTIFIER_TYPE_HD_STATION_NAME, 415 IDENTIFIER_TYPE_DAB_SID_EXT, 416 IDENTIFIER_TYPE_DAB_SIDECC, 417 IDENTIFIER_TYPE_DAB_ENSEMBLE, 418 IDENTIFIER_TYPE_DAB_SCID, 419 IDENTIFIER_TYPE_DAB_FREQUENCY, 420 IDENTIFIER_TYPE_DRMO_SERVICE_ID, 421 IDENTIFIER_TYPE_DRMO_FREQUENCY, 422 IDENTIFIER_TYPE_DRMO_MODULATION, 423 IDENTIFIER_TYPE_SXM_SERVICE_ID, 424 IDENTIFIER_TYPE_SXM_CHANNEL, 425 IDENTIFIER_TYPE_DAB_DMB_SID_EXT, 426 IDENTIFIER_TYPE_HD_STATION_LOCATION, 427 }) 428 @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END) 429 @Retention(RetentionPolicy.SOURCE) 430 public @interface IdentifierType {} 431 432 private final @ProgramType int mProgramType; 433 private final @NonNull Identifier mPrimaryId; 434 private final @NonNull Identifier[] mSecondaryIds; 435 private final @NonNull long[] mVendorIds; 436 437 /** 438 * Constructor for ProgramSelector. 439 * 440 * <p>It's not desired to modify selector objects, so all its fields are initialized at 441 * creation. 442 * 443 * <p>Identifier lists must not contain any nulls, but can itself be null to be interpreted 444 * as empty list at object creation. 445 * 446 * @param programType type of a radio technology. 447 * @param primaryId primary program identifier. 448 * @param secondaryIds list of secondary program identifiers. 449 * @param vendorIds list of vendor-specific program identifiers. 450 */ ProgramSelector(@rogramType int programType, @NonNull Identifier primaryId, @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds)451 public ProgramSelector(@ProgramType int programType, @NonNull Identifier primaryId, 452 @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds) { 453 if (secondaryIds == null) secondaryIds = new Identifier[0]; 454 if (vendorIds == null) vendorIds = new long[0]; 455 if (Stream.of(secondaryIds).anyMatch(id -> id == null)) { 456 throw new IllegalArgumentException("secondaryIds list must not contain nulls"); 457 } 458 mProgramType = programType; 459 mPrimaryId = Objects.requireNonNull(primaryId); 460 mSecondaryIds = secondaryIds; 461 mVendorIds = vendorIds; 462 } 463 464 /** 465 * Type of a radio technology. 466 * 467 * @return program type. 468 * @deprecated use {@link #getPrimaryId} instead 469 */ 470 @Deprecated getProgramType()471 public @ProgramType int getProgramType() { 472 return mProgramType; 473 } 474 475 /** 476 * Primary program identifier uniquely identifies a station and is used to 477 * determine equality between two ProgramSelectors. 478 * 479 * @return primary identifier. 480 */ getPrimaryId()481 public @NonNull Identifier getPrimaryId() { 482 return mPrimaryId; 483 } 484 485 /** 486 * Secondary program identifier is not required for tuning, but may make it 487 * faster or more reliable. 488 * 489 * @return secondary identifier list, must not be modified. 490 */ getSecondaryIds()491 public @NonNull Identifier[] getSecondaryIds() { 492 return mSecondaryIds; 493 } 494 495 /** 496 * Looks up an identifier of a given type (either primary or secondary). 497 * 498 * <p>If there are multiple identifiers if a given type, then first in order (where primary id 499 * is before any secondary) is selected. 500 * 501 * @param type type of identifier. 502 * @return identifier value, if found. 503 * @throws IllegalArgumentException, if not found. 504 */ getFirstId(@dentifierType int type)505 public long getFirstId(@IdentifierType int type) { 506 if (mPrimaryId.getType() == type) return mPrimaryId.getValue(); 507 for (Identifier id : mSecondaryIds) { 508 if (id.getType() == type) return id.getValue(); 509 } 510 throw new IllegalArgumentException("Identifier " + type + " not found"); 511 } 512 513 /** 514 * Looks up all identifier of a given type (either primary or secondary). 515 * 516 * <p>Some identifiers may be provided multiple times, for example 517 * {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} for FM Alternate Frequencies. 518 * 519 * @param type type of identifier. 520 * @return an array of identifiers, generated on each call. May be modified. 521 */ getAllIds(@dentifierType int type)522 public @NonNull Identifier[] getAllIds(@IdentifierType int type) { 523 List<Identifier> out = new ArrayList<>(); 524 525 if (mPrimaryId.getType() == type) out.add(mPrimaryId); 526 for (Identifier id : mSecondaryIds) { 527 if (id.getType() == type) out.add(id); 528 } 529 530 return out.toArray(new Identifier[out.size()]); 531 } 532 533 /** 534 * Vendor identifiers are passed as-is to the HAL implementation, 535 * preserving elements order. 536 * 537 * @return an array of vendor identifiers, must not be modified. 538 * @deprecated for HAL 1.x compatibility; 539 * HAL 2.x uses standard primary/secondary lists for vendor IDs 540 */ 541 @Deprecated getVendorIds()542 public @NonNull long[] getVendorIds() { 543 return mVendorIds; 544 } 545 546 /** 547 * Creates an equivalent ProgramSelector with a given secondary identifier preferred. 548 * 549 * <p>Used to point to a specific physical identifier for technologies that may broadcast the 550 * same program on different channels. For example, with a DAB program broadcasted over multiple 551 * ensembles, the radio hardware may select the one with the strongest signal. The UI may select 552 * preferred ensemble though, so the radio hardware may try to use it in the first place. 553 * 554 * <p>This is a best-effort hint for the tuner, not a guaranteed behavior. 555 * 556 * <p>Setting the given secondary identifier as preferred means filtering out other secondary 557 * identifiers of its type and adding it to the list. 558 * 559 * @param preferred preferred secondary identifier 560 * @return a new ProgramSelector with a given secondary identifier preferred 561 */ withSecondaryPreferred(@onNull Identifier preferred)562 public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) { 563 int preferredType = preferred.getType(); 564 Identifier[] secondaryIds = Stream.concat( 565 // remove other identifiers of that type 566 Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType), 567 // add preferred identifier instead 568 Stream.of(preferred)).toArray(Identifier[]::new); 569 570 return new ProgramSelector( 571 mProgramType, 572 mPrimaryId, 573 secondaryIds, 574 mVendorIds 575 ); 576 } 577 578 /** 579 * Builds new ProgramSelector for AM/FM frequency. 580 * 581 * @param band the band. 582 * @param frequencyKhz the frequency in kHz. 583 * @return new {@link ProgramSelector} object representing given frequency. 584 * @throws IllegalArgumentException if provided frequency is out of bounds. 585 */ createAmFmSelector( @adioManager.Band int band, int frequencyKhz)586 public static @NonNull ProgramSelector createAmFmSelector( 587 @RadioManager.Band int band, int frequencyKhz) { 588 return createAmFmSelector(band, frequencyKhz, 0); 589 } 590 591 /** 592 * Checks, if a given AM/FM frequency is roughly valid and in correct unit. 593 * 594 * <p>It does not check the range precisely: it may provide false positives, but not false 595 * negatives. In particular, it may be way off for certain regions. 596 * The main purpose is to avoid passing improper units, ie. MHz instead of kHz. 597 * 598 * @param isAm {@code true}, if AM, {@code false} if FM. 599 * @param frequencyKhz the frequency in kHz. 600 * @return {@code true}, if the frequency is roughly valid. 601 */ isValidAmFmFrequency(boolean isAm, int frequencyKhz)602 private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) { 603 if (isAm) { 604 return frequencyKhz > 150 && frequencyKhz <= 30000; 605 } else { 606 return frequencyKhz > 60000 && frequencyKhz < 110000; 607 } 608 } 609 610 /** 611 * Builds new ProgramSelector for AM/FM frequency. 612 * 613 * <p>This method variant supports HD Radio subchannels, but it's undesirable to 614 * select them manually. Instead, the value should be retrieved from program list. 615 * 616 * @param band the band. 617 * @param frequencyKhz the frequency in kHz. 618 * @param subChannel 1-based HD Radio subchannel. 619 * @return new ProgramSelector object representing given frequency. 620 * @throws IllegalArgumentException if provided frequency is out of bounds, 621 * or tried setting a subchannel for analog AM/FM. 622 */ createAmFmSelector( @adioManager.Band int band, int frequencyKhz, int subChannel)623 public static @NonNull ProgramSelector createAmFmSelector( 624 @RadioManager.Band int band, int frequencyKhz, int subChannel) { 625 if (band == RadioManager.BAND_INVALID) { 626 // 50MHz is a rough boundary between AM (<30MHz) and FM (>60MHz). 627 if (frequencyKhz < 50000) { 628 band = (subChannel <= 0) ? RadioManager.BAND_AM : RadioManager.BAND_AM_HD; 629 } else { 630 band = (subChannel <= 0) ? RadioManager.BAND_FM : RadioManager.BAND_FM_HD; 631 } 632 } 633 634 boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD); 635 boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD); 636 if (!isAm && !isDigital && band != RadioManager.BAND_FM) { 637 throw new IllegalArgumentException("Unknown band: " + band); 638 } 639 if (subChannel < 0 || subChannel > 8) { 640 throw new IllegalArgumentException("Invalid subchannel: " + subChannel); 641 } 642 if (subChannel > 0 && !isDigital) { 643 throw new IllegalArgumentException("Subchannels are not supported for non-HD radio"); 644 } 645 if (!isValidAmFmFrequency(isAm, frequencyKhz)) { 646 throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency: " 647 + frequencyKhz); 648 } 649 650 // We can't use AM_HD or FM_HD, because we don't know HD station ID. 651 @ProgramType int programType = isAm ? PROGRAM_TYPE_AM : PROGRAM_TYPE_FM; 652 Identifier primary = new Identifier(IDENTIFIER_TYPE_AMFM_FREQUENCY, frequencyKhz); 653 654 Identifier[] secondary = null; 655 if (subChannel > 0) { 656 /* Stating sub channel for non-HD AM/FM does not give any guarantees, 657 * but we can't do much more without HD station ID. 658 * 659 * The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based. 660 */ 661 secondary = new Identifier[]{ 662 new Identifier(IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)}; 663 } 664 665 return new ProgramSelector(programType, primary, secondary, null); 666 } 667 668 @NonNull 669 @Override toString()670 public String toString() { 671 StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType) 672 .append(", primary=").append(mPrimaryId); 673 if (mSecondaryIds.length > 0) { 674 sb.append(", secondary=").append(Arrays.toString(mSecondaryIds)); 675 } 676 if (mVendorIds.length > 0) { 677 sb.append(", vendor=").append(Arrays.toString(mVendorIds)); 678 } 679 sb.append(")"); 680 return sb.toString(); 681 } 682 683 @Override hashCode()684 public int hashCode() { 685 // secondaryIds and vendorIds are ignored for equality/hashing 686 return mPrimaryId.hashCode(); 687 } 688 689 @Override equals(@ullable Object obj)690 public boolean equals(@Nullable Object obj) { 691 if (this == obj) return true; 692 if (!(obj instanceof ProgramSelector)) return false; 693 ProgramSelector other = (ProgramSelector) obj; 694 // secondaryIds and vendorIds are ignored for equality/hashing 695 // programType can be inferred from primaryId, thus not checked 696 return mPrimaryId.equals(other.getPrimaryId()); 697 } 698 699 /** @hide */ strictEquals(@ullable Object obj)700 public boolean strictEquals(@Nullable Object obj) { 701 if (this == obj) return true; 702 if (!(obj instanceof ProgramSelector)) return false; 703 ProgramSelector other = (ProgramSelector) obj; 704 // vendorIds are ignored for equality 705 // programType can be inferred from primaryId, thus not checked 706 return mPrimaryId.equals(other.getPrimaryId()) 707 && mSecondaryIds.length == other.mSecondaryIds.length 708 && Arrays.asList(mSecondaryIds).containsAll( 709 Arrays.asList(other.mSecondaryIds)); 710 } 711 ProgramSelector(Parcel in)712 private ProgramSelector(Parcel in) { 713 mProgramType = in.readInt(); 714 mPrimaryId = in.readTypedObject(Identifier.CREATOR); 715 mSecondaryIds = in.createTypedArray(Identifier.CREATOR); 716 if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) { 717 throw new IllegalArgumentException("secondaryIds list must not contain nulls"); 718 } 719 mVendorIds = in.createLongArray(); 720 } 721 722 @Override writeToParcel(Parcel dest, int flags)723 public void writeToParcel(Parcel dest, int flags) { 724 dest.writeInt(mProgramType); 725 dest.writeTypedObject(mPrimaryId, 0); 726 dest.writeTypedArray(mSecondaryIds, 0); 727 dest.writeLongArray(mVendorIds); 728 } 729 730 @Override describeContents()731 public int describeContents() { 732 return 0; 733 } 734 735 public static final @android.annotation.NonNull Parcelable.Creator<ProgramSelector> CREATOR = 736 new Parcelable.Creator<ProgramSelector>() { 737 public ProgramSelector createFromParcel(Parcel in) { 738 return new ProgramSelector(in); 739 } 740 741 public ProgramSelector[] newArray(int size) { 742 return new ProgramSelector[size]; 743 } 744 }; 745 746 /** 747 * A single program identifier component, e.g. frequency or channel ID. 748 * 749 * <p>The long value field holds the value in format described in comments for 750 * IdentifierType constants. 751 */ 752 public static final class Identifier implements Parcelable { 753 private final @IdentifierType int mType; 754 private final long mValue; 755 Identifier(@dentifierType int type, long value)756 public Identifier(@IdentifierType int type, long value) { 757 if (type == IDENTIFIER_TYPE_HD_STATION_NAME) { 758 // see getType 759 type = IDENTIFIER_TYPE_HD_SUBCHANNEL; 760 } 761 mType = type; 762 mValue = value; 763 } 764 765 /** 766 * Type of an identifier. 767 * 768 * @return type of an identifier. 769 */ getType()770 public @IdentifierType int getType() { 771 if (mType == IDENTIFIER_TYPE_HD_SUBCHANNEL && mValue > 10) { 772 /* HD_SUBCHANNEL and HD_STATION_NAME use the same identifier type, but they differ 773 * in possible values: sub channel is 0-7, station name is greater than ASCII space 774 * code (32). 775 */ 776 return IDENTIFIER_TYPE_HD_STATION_NAME; 777 } 778 return mType; 779 } 780 781 /** 782 * Returns whether this identifier's type is considered a category when filtering 783 * ProgramLists for category entries. 784 * 785 * @see ProgramList.Filter#areCategoriesIncluded 786 * @return {@link false} if this identifier's type is not tunable (e.g. DAB ensemble or 787 * vendor-specified type). {@link true} otherwise. 788 */ isCategoryType()789 public boolean isCategoryType() { 790 return (mType >= IDENTIFIER_TYPE_VENDOR_START && mType <= IDENTIFIER_TYPE_VENDOR_END) 791 || mType == IDENTIFIER_TYPE_DAB_ENSEMBLE; 792 } 793 794 /** 795 * Value of an identifier. 796 * 797 * <p>Its meaning depends on identifier type, ie. for 798 * {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} type, the value is a frequency in kHz. 799 * 800 * <p>The range of a value depends on its type; it does not always require the whole long 801 * range. Casting to necessary type (ie. int) without range checking is correct in front-end 802 * code - any range violations are either errors in the framework or in the 803 * HAL implementation. For example, {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} always fits in 804 * int, as {@link Integer#MAX_VALUE} would mean 2.1THz. 805 * 806 * @return value of an identifier. 807 */ getValue()808 public long getValue() { 809 return mValue; 810 } 811 812 @NonNull 813 @Override toString()814 public String toString() { 815 return "Identifier(" + mType + ", " + mValue + ")"; 816 } 817 818 @Override hashCode()819 public int hashCode() { 820 return Objects.hash(mType, mValue); 821 } 822 823 @Override equals(@ullable Object obj)824 public boolean equals(@Nullable Object obj) { 825 if (this == obj) return true; 826 if (!(obj instanceof Identifier)) return false; 827 Identifier other = (Identifier) obj; 828 return other.getType() == mType && other.getValue() == mValue; 829 } 830 Identifier(Parcel in)831 private Identifier(Parcel in) { 832 mType = in.readInt(); 833 mValue = in.readLong(); 834 } 835 836 @Override writeToParcel(Parcel dest, int flags)837 public void writeToParcel(Parcel dest, int flags) { 838 dest.writeInt(mType); 839 dest.writeLong(mValue); 840 } 841 842 @Override describeContents()843 public int describeContents() { 844 return 0; 845 } 846 847 public static final @android.annotation.NonNull Parcelable.Creator<Identifier> CREATOR = 848 new Parcelable.Creator<Identifier>() { 849 public Identifier createFromParcel(Parcel in) { 850 return new Identifier(in); 851 } 852 853 public Identifier[] newArray(int size) { 854 return new Identifier[size]; 855 } 856 }; 857 } 858 } 859