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