| /** |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.hardware.radio; |
| |
| import android.annotation.FlaggedApi; |
| import android.annotation.IntDef; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SystemApi; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.stream.Stream; |
| |
| /** |
| * A set of identifiers necessary to tune to a given station. |
| * |
| * <p>This can hold various identifiers, like |
| * <ui> |
| * <li>AM/FM frequency</li> |
| * <li>HD Radio subchannel</li> |
| * <li>DAB channel info</li> |
| * </ui> |
| * |
| * <p>The primary ID uniquely identifies a station and can be used for equality |
| * check. The secondary IDs are supplementary and can speed up tuning process, |
| * but the primary ID is sufficient (ie. after a full band scan). |
| * |
| * <p>Two selectors with different secondary IDs, but the same primary ID are |
| * considered equal. In particular, secondary IDs vector may get updated for |
| * an entry on the program list (ie. when a better frequency for a given |
| * station is found). |
| * |
| * <p>The primaryId of a given programType MUST be of a specific type: |
| * <ui> |
| * <li>AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;</li> |
| * <li>AM_HD, FM_HD: HD_STATION_ID_EXT;</li> |
| * <li>DAB: DAB_SIDECC;</li> |
| * <li>DRMO: DRMO_SERVICE_ID;</li> |
| * <li>SXM: SXM_SERVICE_ID;</li> |
| * <li>VENDOR: VENDOR_PRIMARY.</li> |
| * </ui> |
| * @hide |
| */ |
| @SystemApi |
| public final class ProgramSelector implements Parcelable { |
| /** Invalid program type. |
| * @deprecated use {@link IdentifierType} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_INVALID = 0; |
| /** Analog AM radio (with or without RDS). |
| * @deprecated use {@link IdentifierType} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_AM = 1; |
| /** analog FM radio (with or without RDS). |
| * @deprecated use {@link IdentifierType} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_FM = 2; |
| /** AM HD Radio. |
| * @deprecated use {@link Identifier} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_AM_HD = 3; |
| /** FM HD Radio. |
| * @deprecated use {@link Identifier} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_FM_HD = 4; |
| /** Digital audio broadcasting. |
| * @deprecated use {@link Identifier} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_DAB = 5; |
| /** Digital Radio Mondiale. |
| * @deprecated use {@link Identifier} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_DRMO = 6; |
| /** SiriusXM Satellite Radio. |
| * @deprecated use {@link Identifier} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_SXM = 7; |
| /** Vendor-specific, not synced across devices. |
| * @deprecated use {@link Identifier} instead |
| */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_VENDOR_START = 1000; |
| /** @deprecated use {@link Identifier} instead */ |
| @Deprecated |
| public static final int PROGRAM_TYPE_VENDOR_END = 1999; |
| /** |
| * @deprecated use {@link Identifier} instead |
| * @removed mistakenly exposed previously |
| */ |
| @Deprecated |
| @IntDef(prefix = { "PROGRAM_TYPE_" }, value = { |
| PROGRAM_TYPE_INVALID, |
| PROGRAM_TYPE_AM, |
| PROGRAM_TYPE_FM, |
| PROGRAM_TYPE_AM_HD, |
| PROGRAM_TYPE_FM_HD, |
| PROGRAM_TYPE_DAB, |
| PROGRAM_TYPE_DRMO, |
| PROGRAM_TYPE_SXM, |
| }) |
| @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface ProgramType {} |
| |
| /** |
| * Bitmask for HD radio subchannel 1 |
| * |
| * <p>There are at most 8 HD radio subchannels of 1-based om HD radio standard. It is |
| * converted to 0-based index. 0 is the index of main program service (MPS). 1 to 7 are |
| * indexes of additional supplemental program services (SPS). |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_1 = 1 << 0; |
| |
| /** |
| * Bitmask for HD radio subchannel 2 |
| * |
| * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_2 = 1 << 1; |
| |
| /** |
| * Bitmask for HD radio subchannel 3 |
| * |
| * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_3 = 1 << 2; |
| |
| /** |
| * Bitmask for HD radio subchannel 4 |
| * |
| * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_4 = 1 << 3; |
| |
| /** |
| * Bitmask for HD radio subchannel 5 |
| * |
| * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_5 = 1 << 4; |
| |
| /** |
| * Bitmask for HD radio subchannel 6 |
| * |
| * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_6 = 1 << 5; |
| |
| /** |
| * Bitmask for HD radio subchannel 7 |
| * |
| * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_7 = 1 << 6; |
| |
| /** |
| * Bitmask for HD radio subchannel 8 |
| * |
| * <p>For further reference, see {@link #SUB_CHANNEL_HD_1} |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int SUB_CHANNEL_HD_8 = 1 << 7; |
| |
| /** @hide */ |
| @IntDef(prefix = { "SUB_CHANNEL_HD_" }, value = { |
| SUB_CHANNEL_HD_1, |
| SUB_CHANNEL_HD_2, |
| SUB_CHANNEL_HD_3, |
| SUB_CHANNEL_HD_4, |
| SUB_CHANNEL_HD_5, |
| SUB_CHANNEL_HD_6, |
| SUB_CHANNEL_HD_7, |
| SUB_CHANNEL_HD_8, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface HdSubChannel {} |
| |
| public static final int IDENTIFIER_TYPE_INVALID = 0; |
| /** |
| * Primary identifier for analog (without RDS) AM/FM stations: |
| * frequency in kHz. |
| * |
| * <p>This identifier also contains band information: |
| * <li> |
| * <ul><500kHz: AM LW. |
| * <ul>500kHz - 1705kHz: AM MW. |
| * <ul>1.71MHz - 30MHz: AM SW. |
| * <ul>>60MHz: FM. |
| * </li> |
| */ |
| public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; |
| /** |
| * 16bit primary identifier for FM RDS station. |
| */ |
| public static final int IDENTIFIER_TYPE_RDS_PI = 2; |
| /** |
| * 64bit compound primary identifier for HD Radio. |
| * |
| * <p>Consists of (from the LSB): |
| * <li> |
| * <ul>32bit: Station ID number.</ul> |
| * <ul>4bit: HD subchannel, see {@link #SUB_CHANNEL_HD_1}.</ul> |
| * <ul>18bit: AMFM_FREQUENCY.</ul> |
| * </li> |
| * |
| * <p>While station ID number should be unique globally, it sometimes gets |
| * abused by broadcasters (i.e. not being set at all). To ensure local |
| * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is |
| * a best-effort - see {@link #IDENTIFIER_TYPE_HD_STATION_NAME}. |
| * |
| * <p>The remaining bits should be set to zeros when writing on the chip side |
| * and ignored when read. |
| */ |
| public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; |
| /** |
| * HD Radio subchannel - a value in range of 0-7. |
| * |
| * <p>The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS), |
| * as opposed to HD Radio standard (where it's 1-based). |
| * |
| * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; |
| /** |
| * 64bit additional identifier for HD Radio. |
| * |
| * <p>Due to Station ID abuse, some {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT} |
| * identifiers may be not globally unique. To provide a best-effort solution, a |
| * short version of station name may be carried as additional identifier and |
| * may be used by the tuner hardware to double-check tuning. |
| * |
| * <p>The name is limited to the first 8 A-Z0-9 characters (lowercase |
| * letters must be converted to uppercase). Encoded in little-endian |
| * ASCII: the first character of the name is the LSB. |
| * |
| * <p>For example: "Abc" is encoded as 0x434241. |
| */ |
| public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; |
| /** |
| * @see #IDENTIFIER_TYPE_DAB_SID_EXT |
| * |
| * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; |
| /** |
| * 28bit compound primary identifier for Digital Audio Broadcasting. |
| * |
| * <p>Consists of (from the LSB): |
| * <li> |
| * <ul>16bit: SId.</ul> |
| * <ul>8bit: ECC code.</ul> |
| * <ul>4bit: SCIdS.</ul> |
| * </li> |
| * |
| * <p>SCIdS (Service Component Identifier within the Service) value |
| * of 0 represents the main service, while 1 and above represents |
| * secondary services. |
| * |
| * <p>The remaining bits should be set to zeros when writing on the chip |
| * side and ignored when read. |
| * |
| * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC; |
| /** 16bit */ |
| public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; |
| /** 12bit */ |
| public static final int IDENTIFIER_TYPE_DAB_SCID = 7; |
| /** kHz */ |
| public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; |
| /** |
| * 24bit primary identifier for Digital Radio Mondiale. |
| */ |
| public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; |
| /** kHz */ |
| public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; |
| /** |
| * 1: AM, 2:FM |
| * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; |
| /** |
| * 32bit primary identifier for SiriusXM Satellite Radio. |
| * |
| * @deprecated SiriusXM Satellite Radio is not supported |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; |
| /** |
| * 0-999 range |
| * |
| * @deprecated SiriusXM Satellite Radio is not supported |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; |
| /** |
| * 44bit compound primary identifier for Digital Audio Broadcasting and |
| * Digital Multimedia Broadcasting. |
| * |
| * <p>Consists of (from the LSB): |
| * <li> |
| * <ul>32bit: SId;</ul> |
| * <ul>8bit: ECC code;</ul> |
| * <ul>4bit: SCIdS.</ul> |
| * </li> |
| * |
| * <p>SCIdS (Service Component Identifier within the Service) value |
| * of 0 represents the main service, while 1 and above represents |
| * secondary services. |
| * |
| * <p>The remaining bits should be set to zeros when writing on the chip |
| * side and ignored when read. |
| */ |
| public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; |
| /** |
| * 64bit additional identifier for HD Radio representing station location. |
| * |
| * <p>Consists of (from the LSB): |
| * <li> |
| * <ul>4 bit: Bits 0:3 of altitude</ul> |
| * <ul>13 bit: Fractional bits of longitude</ul> |
| * <ul>8 bit: Integer bits of longitude</ul> |
| * <ul>1 bit: 0 for east and 1 for west for longitude</ul> |
| * <ul>1 bit: 0, representing longitude</ul> |
| * <ul>5 bit: pad of zeros separating longitude and latitude</ul> |
| * <ul>4 bit: Bits 4:7 of altitude</ul> |
| * <ul>13 bit: Fractional bits of latitude</ul> |
| * <ul>8 bit: Integer bits of latitude</ul> |
| * <ul>1 bit: 0 for north and 1 for south for latitude</ul> |
| * <ul>1 bit: 1, representing latitude</ul> |
| * <ul>5 bit: pad of zeros</ul> |
| * </li> |
| * |
| * <p>This format is defined in NRSC-5-C document: SY_IDD_1020s. |
| * |
| * <p>Due to Station ID abuse, some |
| * {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT} identifiers may be not |
| * globally unique. To provide a best-effort solution, the station’s |
| * broadcast antenna containing the latitude and longitude may be |
| * carried as additional identifier and may be used by the tuner hardware |
| * to double-check tuning. |
| */ |
| @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) |
| public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; |
| /** |
| * Primary identifier for vendor-specific radio technology. |
| * The value format is determined by a vendor. |
| * |
| * <p>It must not be used in any other programType than corresponding VENDOR |
| * type between VENDOR_START and VENDOR_END (e.g. identifier type 1015 must |
| * not be used in any program type other than 1015). |
| */ |
| public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START; |
| /** |
| * @see #IDENTIFIER_TYPE_VENDOR_START |
| */ |
| public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END; |
| /** |
| * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_START} instead |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START; |
| /** |
| * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_END} instead |
| */ |
| @Deprecated |
| public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END; |
| /** @removed mistakenly exposed previously */ |
| @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = { |
| IDENTIFIER_TYPE_INVALID, |
| IDENTIFIER_TYPE_AMFM_FREQUENCY, |
| IDENTIFIER_TYPE_RDS_PI, |
| IDENTIFIER_TYPE_HD_STATION_ID_EXT, |
| IDENTIFIER_TYPE_HD_SUBCHANNEL, |
| IDENTIFIER_TYPE_HD_STATION_NAME, |
| IDENTIFIER_TYPE_DAB_SID_EXT, |
| IDENTIFIER_TYPE_DAB_SIDECC, |
| IDENTIFIER_TYPE_DAB_ENSEMBLE, |
| IDENTIFIER_TYPE_DAB_SCID, |
| IDENTIFIER_TYPE_DAB_FREQUENCY, |
| IDENTIFIER_TYPE_DRMO_SERVICE_ID, |
| IDENTIFIER_TYPE_DRMO_FREQUENCY, |
| IDENTIFIER_TYPE_DRMO_MODULATION, |
| IDENTIFIER_TYPE_SXM_SERVICE_ID, |
| IDENTIFIER_TYPE_SXM_CHANNEL, |
| IDENTIFIER_TYPE_DAB_DMB_SID_EXT, |
| IDENTIFIER_TYPE_HD_STATION_LOCATION, |
| }) |
| @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface IdentifierType {} |
| |
| private final @ProgramType int mProgramType; |
| private final @NonNull Identifier mPrimaryId; |
| private final @NonNull Identifier[] mSecondaryIds; |
| private final @NonNull long[] mVendorIds; |
| |
| /** |
| * Constructor for ProgramSelector. |
| * |
| * <p>It's not desired to modify selector objects, so all its fields are initialized at |
| * creation. |
| * |
| * <p>Identifier lists must not contain any nulls, but can itself be null to be interpreted |
| * as empty list at object creation. |
| * |
| * @param programType type of a radio technology. |
| * @param primaryId primary program identifier. |
| * @param secondaryIds list of secondary program identifiers. |
| * @param vendorIds list of vendor-specific program identifiers. |
| */ |
| public ProgramSelector(@ProgramType int programType, @NonNull Identifier primaryId, |
| @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds) { |
| if (secondaryIds == null) secondaryIds = new Identifier[0]; |
| if (vendorIds == null) vendorIds = new long[0]; |
| if (Stream.of(secondaryIds).anyMatch(id -> id == null)) { |
| throw new IllegalArgumentException("secondaryIds list must not contain nulls"); |
| } |
| mProgramType = programType; |
| mPrimaryId = Objects.requireNonNull(primaryId); |
| mSecondaryIds = secondaryIds; |
| mVendorIds = vendorIds; |
| } |
| |
| /** |
| * Type of a radio technology. |
| * |
| * @return program type. |
| * @deprecated use {@link #getPrimaryId} instead |
| */ |
| @Deprecated |
| public @ProgramType int getProgramType() { |
| return mProgramType; |
| } |
| |
| /** |
| * Primary program identifier uniquely identifies a station and is used to |
| * determine equality between two ProgramSelectors. |
| * |
| * @return primary identifier. |
| */ |
| public @NonNull Identifier getPrimaryId() { |
| return mPrimaryId; |
| } |
| |
| /** |
| * Secondary program identifier is not required for tuning, but may make it |
| * faster or more reliable. |
| * |
| * @return secondary identifier list, must not be modified. |
| */ |
| public @NonNull Identifier[] getSecondaryIds() { |
| return mSecondaryIds; |
| } |
| |
| /** |
| * Looks up an identifier of a given type (either primary or secondary). |
| * |
| * <p>If there are multiple identifiers if a given type, then first in order (where primary id |
| * is before any secondary) is selected. |
| * |
| * @param type type of identifier. |
| * @return identifier value, if found. |
| * @throws IllegalArgumentException, if not found. |
| */ |
| public long getFirstId(@IdentifierType int type) { |
| if (mPrimaryId.getType() == type) return mPrimaryId.getValue(); |
| for (Identifier id : mSecondaryIds) { |
| if (id.getType() == type) return id.getValue(); |
| } |
| throw new IllegalArgumentException("Identifier " + type + " not found"); |
| } |
| |
| /** |
| * Looks up all identifier of a given type (either primary or secondary). |
| * |
| * <p>Some identifiers may be provided multiple times, for example |
| * {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} for FM Alternate Frequencies. |
| * |
| * @param type type of identifier. |
| * @return an array of identifiers, generated on each call. May be modified. |
| */ |
| public @NonNull Identifier[] getAllIds(@IdentifierType int type) { |
| List<Identifier> out = new ArrayList<>(); |
| |
| if (mPrimaryId.getType() == type) out.add(mPrimaryId); |
| for (Identifier id : mSecondaryIds) { |
| if (id.getType() == type) out.add(id); |
| } |
| |
| return out.toArray(new Identifier[out.size()]); |
| } |
| |
| /** |
| * Vendor identifiers are passed as-is to the HAL implementation, |
| * preserving elements order. |
| * |
| * @return an array of vendor identifiers, must not be modified. |
| * @deprecated for HAL 1.x compatibility; |
| * HAL 2.x uses standard primary/secondary lists for vendor IDs |
| */ |
| @Deprecated |
| public @NonNull long[] getVendorIds() { |
| return mVendorIds; |
| } |
| |
| /** |
| * Creates an equivalent ProgramSelector with a given secondary identifier preferred. |
| * |
| * <p>Used to point to a specific physical identifier for technologies that may broadcast the |
| * same program on different channels. For example, with a DAB program broadcasted over multiple |
| * ensembles, the radio hardware may select the one with the strongest signal. The UI may select |
| * preferred ensemble though, so the radio hardware may try to use it in the first place. |
| * |
| * <p>This is a best-effort hint for the tuner, not a guaranteed behavior. |
| * |
| * <p>Setting the given secondary identifier as preferred means filtering out other secondary |
| * identifiers of its type and adding it to the list. |
| * |
| * @param preferred preferred secondary identifier |
| * @return a new ProgramSelector with a given secondary identifier preferred |
| */ |
| public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) { |
| int preferredType = preferred.getType(); |
| Identifier[] secondaryIds = Stream.concat( |
| // remove other identifiers of that type |
| Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType), |
| // add preferred identifier instead |
| Stream.of(preferred)).toArray(Identifier[]::new); |
| |
| return new ProgramSelector( |
| mProgramType, |
| mPrimaryId, |
| secondaryIds, |
| mVendorIds |
| ); |
| } |
| |
| /** |
| * Builds new ProgramSelector for AM/FM frequency. |
| * |
| * @param band the band. |
| * @param frequencyKhz the frequency in kHz. |
| * @return new {@link ProgramSelector} object representing given frequency. |
| * @throws IllegalArgumentException if provided frequency is out of bounds. |
| */ |
| public static @NonNull ProgramSelector createAmFmSelector( |
| @RadioManager.Band int band, int frequencyKhz) { |
| return createAmFmSelector(band, frequencyKhz, 0); |
| } |
| |
| /** |
| * Checks, if a given AM/FM frequency is roughly valid and in correct unit. |
| * |
| * <p>It does not check the range precisely: it may provide false positives, but not false |
| * negatives. In particular, it may be way off for certain regions. |
| * The main purpose is to avoid passing improper units, ie. MHz instead of kHz. |
| * |
| * @param isAm true, if AM, false if FM. |
| * @param frequencyKhz the frequency in kHz. |
| * @return true, if the frequency is roughly valid. |
| */ |
| private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) { |
| if (isAm) { |
| return frequencyKhz > 150 && frequencyKhz <= 30000; |
| } else { |
| return frequencyKhz > 60000 && frequencyKhz < 110000; |
| } |
| } |
| |
| /** |
| * Builds new ProgramSelector for AM/FM frequency. |
| * |
| * <p>This method variant supports HD Radio subchannels, but it's undesirable to |
| * select them manually. Instead, the value should be retrieved from program list. |
| * |
| * @param band the band. |
| * @param frequencyKhz the frequency in kHz. |
| * @param subChannel 1-based HD Radio subchannel. |
| * @return new ProgramSelector object representing given frequency. |
| * @throws IllegalArgumentException if provided frequency is out of bounds, |
| * or tried setting a subchannel for analog AM/FM. |
| */ |
| public static @NonNull ProgramSelector createAmFmSelector( |
| @RadioManager.Band int band, int frequencyKhz, int subChannel) { |
| if (band == RadioManager.BAND_INVALID) { |
| // 50MHz is a rough boundary between AM (<30MHz) and FM (>60MHz). |
| if (frequencyKhz < 50000) { |
| band = (subChannel <= 0) ? RadioManager.BAND_AM : RadioManager.BAND_AM_HD; |
| } else { |
| band = (subChannel <= 0) ? RadioManager.BAND_FM : RadioManager.BAND_FM_HD; |
| } |
| } |
| |
| boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD); |
| boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD); |
| if (!isAm && !isDigital && band != RadioManager.BAND_FM) { |
| throw new IllegalArgumentException("Unknown band: " + band); |
| } |
| if (subChannel < 0 || subChannel > 8) { |
| throw new IllegalArgumentException("Invalid subchannel: " + subChannel); |
| } |
| if (subChannel > 0 && !isDigital) { |
| throw new IllegalArgumentException("Subchannels are not supported for non-HD radio"); |
| } |
| if (!isValidAmFmFrequency(isAm, frequencyKhz)) { |
| throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency: " |
| + frequencyKhz); |
| } |
| |
| // We can't use AM_HD or FM_HD, because we don't know HD station ID. |
| @ProgramType int programType = isAm ? PROGRAM_TYPE_AM : PROGRAM_TYPE_FM; |
| Identifier primary = new Identifier(IDENTIFIER_TYPE_AMFM_FREQUENCY, frequencyKhz); |
| |
| Identifier[] secondary = null; |
| if (subChannel > 0) { |
| /* Stating sub channel for non-HD AM/FM does not give any guarantees, |
| * but we can't do much more without HD station ID. |
| * |
| * The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based. |
| */ |
| secondary = new Identifier[]{ |
| new Identifier(IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)}; |
| } |
| |
| return new ProgramSelector(programType, primary, secondary, null); |
| } |
| |
| @NonNull |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType) |
| .append(", primary=").append(mPrimaryId); |
| if (mSecondaryIds.length > 0) { |
| sb.append(", secondary=").append(Arrays.toString(mSecondaryIds)); |
| } |
| if (mVendorIds.length > 0) { |
| sb.append(", vendor=").append(Arrays.toString(mVendorIds)); |
| } |
| sb.append(")"); |
| return sb.toString(); |
| } |
| |
| @Override |
| public int hashCode() { |
| // secondaryIds and vendorIds are ignored for equality/hashing |
| return mPrimaryId.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) return true; |
| if (!(obj instanceof ProgramSelector)) return false; |
| ProgramSelector other = (ProgramSelector) obj; |
| // secondaryIds and vendorIds are ignored for equality/hashing |
| // programType can be inferred from primaryId, thus not checked |
| return mPrimaryId.equals(other.getPrimaryId()); |
| } |
| |
| /** @hide */ |
| public boolean strictEquals(@Nullable Object obj) { |
| if (this == obj) return true; |
| if (!(obj instanceof ProgramSelector)) return false; |
| ProgramSelector other = (ProgramSelector) obj; |
| // vendorIds are ignored for equality |
| // programType can be inferred from primaryId, thus not checked |
| return mPrimaryId.equals(other.getPrimaryId()) |
| && mSecondaryIds.length == other.mSecondaryIds.length |
| && Arrays.asList(mSecondaryIds).containsAll( |
| Arrays.asList(other.mSecondaryIds)); |
| } |
| |
| private ProgramSelector(Parcel in) { |
| mProgramType = in.readInt(); |
| mPrimaryId = in.readTypedObject(Identifier.CREATOR); |
| mSecondaryIds = in.createTypedArray(Identifier.CREATOR); |
| if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) { |
| throw new IllegalArgumentException("secondaryIds list must not contain nulls"); |
| } |
| mVendorIds = in.createLongArray(); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mProgramType); |
| dest.writeTypedObject(mPrimaryId, 0); |
| dest.writeTypedArray(mSecondaryIds, 0); |
| dest.writeLongArray(mVendorIds); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| public static final @android.annotation.NonNull Parcelable.Creator<ProgramSelector> CREATOR = |
| new Parcelable.Creator<ProgramSelector>() { |
| public ProgramSelector createFromParcel(Parcel in) { |
| return new ProgramSelector(in); |
| } |
| |
| public ProgramSelector[] newArray(int size) { |
| return new ProgramSelector[size]; |
| } |
| }; |
| |
| /** |
| * A single program identifier component, e.g. frequency or channel ID. |
| * |
| * <p>The long value field holds the value in format described in comments for |
| * IdentifierType constants. |
| */ |
| public static final class Identifier implements Parcelable { |
| private final @IdentifierType int mType; |
| private final long mValue; |
| |
| public Identifier(@IdentifierType int type, long value) { |
| if (type == IDENTIFIER_TYPE_HD_STATION_NAME) { |
| // see getType |
| type = IDENTIFIER_TYPE_HD_SUBCHANNEL; |
| } |
| mType = type; |
| mValue = value; |
| } |
| |
| /** |
| * Type of an identifier. |
| * |
| * @return type of an identifier. |
| */ |
| public @IdentifierType int getType() { |
| if (mType == IDENTIFIER_TYPE_HD_SUBCHANNEL && mValue > 10) { |
| /* HD_SUBCHANNEL and HD_STATION_NAME use the same identifier type, but they differ |
| * in possible values: sub channel is 0-7, station name is greater than ASCII space |
| * code (32). |
| */ |
| return IDENTIFIER_TYPE_HD_STATION_NAME; |
| } |
| return mType; |
| } |
| |
| /** |
| * Returns whether this identifier's type is considered a category when filtering |
| * ProgramLists for category entries. |
| * |
| * @see ProgramList.Filter#areCategoriesIncluded |
| * @return False if this identifier's type is not tunable (e.g. DAB ensemble or |
| * vendor-specified type). True otherwise. |
| */ |
| public boolean isCategoryType() { |
| return (mType >= IDENTIFIER_TYPE_VENDOR_START && mType <= IDENTIFIER_TYPE_VENDOR_END) |
| || mType == IDENTIFIER_TYPE_DAB_ENSEMBLE; |
| } |
| |
| /** |
| * Value of an identifier. |
| * |
| * <p>Its meaning depends on identifier type, ie. for |
| * {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} type, the value is a frequency in kHz. |
| * |
| * <p>The range of a value depends on its type; it does not always require the whole long |
| * range. Casting to necessary type (ie. int) without range checking is correct in front-end |
| * code - any range violations are either errors in the framework or in the |
| * HAL implementation. For example, {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} always fits in |
| * int, as {@link Integer#MAX_VALUE} would mean 2.1THz. |
| * |
| * @return value of an identifier. |
| */ |
| public long getValue() { |
| return mValue; |
| } |
| |
| @NonNull |
| @Override |
| public String toString() { |
| return "Identifier(" + mType + ", " + mValue + ")"; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mType, mValue); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) return true; |
| if (!(obj instanceof Identifier)) return false; |
| Identifier other = (Identifier) obj; |
| return other.getType() == mType && other.getValue() == mValue; |
| } |
| |
| private Identifier(Parcel in) { |
| mType = in.readInt(); |
| mValue = in.readLong(); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mType); |
| dest.writeLong(mValue); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| public static final @android.annotation.NonNull Parcelable.Creator<Identifier> CREATOR = |
| new Parcelable.Creator<Identifier>() { |
| public Identifier createFromParcel(Parcel in) { |
| return new Identifier(in); |
| } |
| |
| public Identifier[] newArray(int size) { |
| return new Identifier[size]; |
| } |
| }; |
| } |
| } |