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