diff options
11 files changed, 803 insertions, 88 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 836a0166f3ce..29f4f974e4c6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5584,7 +5584,8 @@ package android.hardware.radio { method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void close(); - method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); + method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier); method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback); method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback); method public void removeOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener); @@ -5638,12 +5639,13 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf field public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; // 0x2714 field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 - field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd - field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8 @@ -5658,6 +5660,14 @@ package android.hardware.radio { field @Deprecated public static final int PROGRAM_TYPE_SXM = 7; // 0x7 field @Deprecated public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf field @Deprecated public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_1 = 1; // 0x1 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_2 = 2; // 0x2 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_3 = 4; // 0x4 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_4 = 8; // 0x8 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_5 = 16; // 0x10 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_6 = 32; // 0x20 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_7 = 64; // 0x40 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_8 = 128; // 0x80 } public static final class ProgramSelector.Identifier implements android.os.Parcelable { @@ -5670,7 +5680,7 @@ package android.hardware.radio { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR; } - @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { + @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { } @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType { @@ -5694,7 +5704,9 @@ package android.hardware.radio { field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8 field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7 field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9 - field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 + field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3 field public static final int CONFIG_FORCE_MONO = 1; // 0x1 field public static final int CONFIG_RDS_AF = 4; // 0x4 @@ -5822,8 +5834,11 @@ package android.hardware.radio { method @Deprecated public int getSubChannel(); method @NonNull public java.util.Map<java.lang.String,java.lang.String> getVendorInfo(); method @Deprecated public boolean isDigital(); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdAudioAvailable(); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdSisAvailable(); method public boolean isLive(); method public boolean isMuted(); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isSignalAcquired(); method public boolean isStereo(); method public boolean isTrafficAnnouncementActive(); method public boolean isTrafficProgram(); @@ -5839,6 +5854,7 @@ package android.hardware.radio { method public android.hardware.radio.RadioMetadata.Clock getClock(String); method public int getInt(String); method public String getString(String); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public String[] getStringArray(@NonNull String); method public java.util.Set<java.lang.String> keySet(); method public int size(); method public void writeToParcel(android.os.Parcel, int); @@ -5847,6 +5863,9 @@ package android.hardware.radio { field public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART"; field public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST"; field public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMERCIAL = "android.hardware.radio.metadata.COMMERCIAL"; field public static final String METADATA_KEY_DAB_COMPONENT_NAME = "android.hardware.radio.metadata.DAB_COMPONENT_NAME"; field public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT"; field public static final String METADATA_KEY_DAB_ENSEMBLE_NAME = "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME"; @@ -5854,6 +5873,9 @@ package android.hardware.radio { field public static final String METADATA_KEY_DAB_SERVICE_NAME = "android.hardware.radio.metadata.DAB_SERVICE_NAME"; field public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT = "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT"; field public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_LONG = "android.hardware.radio.metadata.HD_STATION_NAME_LONG"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_SHORT = "android.hardware.radio.metadata.HD_STATION_NAME_SHORT"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE"; field public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON"; field public static final String METADATA_KEY_PROGRAM_NAME = "android.hardware.radio.metadata.PROGRAM_NAME"; field public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY"; @@ -5862,6 +5884,7 @@ package android.hardware.radio { field public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY"; field public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT"; field public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE"; + field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS"; } public static final class RadioMetadata.Builder { @@ -5872,6 +5895,7 @@ package android.hardware.radio { method public android.hardware.radio.RadioMetadata.Builder putClock(String, long, int); method public android.hardware.radio.RadioMetadata.Builder putInt(String, int); method public android.hardware.radio.RadioMetadata.Builder putString(String, String); + method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public android.hardware.radio.RadioMetadata.Builder putStringArray(@NonNull String, @NonNull String[]); } public static final class RadioMetadata.Clock implements android.os.Parcelable { diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index 4f07acf6961a..c5167dbc7d4c 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -17,6 +17,7 @@ package android.hardware.radio; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -45,7 +46,7 @@ public final class ProgramList implements AutoCloseable { private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, + private final ArrayMap<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>> mPrograms = new ArrayMap<>(); @GuardedBy("mLock") @@ -203,11 +204,11 @@ public final class ProgramList implements AutoCloseable { listCallbacksCopied = new ArrayList<>(mListCallbacks); if (chunk.isPurge()) { - Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, - RadioManager.ProgramInfo>>> programsIterator = - mPrograms.entrySet().iterator(); + Iterator<Map.Entry<ProgramSelector.Identifier, + ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>>> + programsIterator = mPrograms.entrySet().iterator(); while (programsIterator.hasNext()) { - Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, + Map.Entry<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>> removed = programsIterator.next(); if (removed.getValue() != null) { removedList.add(removed.getKey()); @@ -270,8 +271,7 @@ public final class ProgramList implements AutoCloseable { if (!mPrograms.containsKey(primaryKey)) { return; } - Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms - .get(primaryKey); + Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms.get(primaryKey); RadioManager.ProgramInfo removed = entries.remove(Objects.requireNonNull(key)); if (removed == null) return; if (entries.size() == 0) { @@ -287,15 +287,10 @@ public final class ProgramList implements AutoCloseable { public @NonNull List<RadioManager.ProgramInfo> toList() { List<RadioManager.ProgramInfo> list = new ArrayList<>(); synchronized (mLock) { - Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier, - RadioManager.ProgramInfo>>> listIterator = mPrograms.entrySet().iterator(); - while (listIterator.hasNext()) { - Iterator<Map.Entry<UniqueProgramIdentifier, - RadioManager.ProgramInfo>> prorgramsIterator = listIterator.next() - .getValue().entrySet().iterator(); - while (prorgramsIterator.hasNext()) { - list.add(prorgramsIterator.next().getValue()); - } + for (int index = 0; index < mPrograms.size(); index++) { + ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = + mPrograms.valueAt(index); + list.addAll(entries.values()); } } return list; @@ -304,9 +299,16 @@ public final class ProgramList implements AutoCloseable { /** * Returns the program with a specified primary identifier. * + * <p>This method only returns the first program from the list return from + * {@link #getProgramInfos} + * * @param id primary identifier of a program to fetch * @return the program info, or null if there is no such program on the list + * + * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs + * with the given primary identifier */ + @Deprecated public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries; synchronized (mLock) { @@ -320,6 +322,29 @@ public final class ProgramList implements AutoCloseable { } /** + * Returns the program list with a specified primary identifier. + * + * @param id primary identifier of a program to fetch + * @return the program info list with the primary identifier, or empty list if there is no such + * program identifier on the list + * @throws NullPointerException if primary identifier is {@code null} + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public @NonNull List<RadioManager.ProgramInfo> getProgramInfos( + @NonNull ProgramSelector.Identifier id) { + Objects.requireNonNull(id, "Primary identifier can not be null"); + ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries; + synchronized (mLock) { + entries = mPrograms.get(id); + } + + if (entries == null) { + return new ArrayList<>(); + } + return new ArrayList<>(entries.values()); + } + + /** * Filter for the program list. */ public static final class Filter implements Parcelable { diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 12442ba12044..c7ec052b309b 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -16,6 +16,7 @@ package android.hardware.radio; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -124,6 +125,86 @@ public final class ProgramSelector implements Parcelable { @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: @@ -147,19 +228,15 @@ public final class ProgramSelector implements Parcelable { * * <p>Consists of (from the LSB): * <li> - * <ul>32bit: Station ID number. - * <ul>4bit: HD_SUBCHANNEL. - * <ul>18bit: AMFM_FREQUENCY. + * <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>HD Radio subchannel is a value in range of 0-7. - * This index is 0-based (where 0 is MPS and 1..7 are SPS), - * as opposed to HD Radio standard (where it's 1-based). + * 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. @@ -202,9 +279,9 @@ public final class ProgramSelector implements Parcelable { * * <p>Consists of (from the LSB): * <li> - * <ul>16bit: SId. - * <ul>8bit: ECC code. - * <ul>4bit: SCIdS. + * <ul>16bit: SId.</ul> + * <ul>8bit: ECC code.</ul> + * <ul>4bit: SCIdS.</ul> * </li> * * <p>SCIdS (Service Component Identifier within the Service) value @@ -238,18 +315,26 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; /** * 32bit primary identifier for SiriusXM Satellite Radio. + * + * @deprecated SiriusXM Satellite Radio is not supported */ public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; - /** 0-999 range */ + /** + * 0-999 range + * + * @deprecated SiriusXM Satellite Radio is not supported + */ 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): - * - 32bit: SId; - * - 8bit: ECC code; - * - 4bit: SCIdS. + * <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 @@ -260,6 +345,36 @@ public final class ProgramSelector implements Parcelable { */ 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. * @@ -300,6 +415,7 @@ public final class ProgramSelector implements Parcelable { 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) diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 8c6083ce49b6..237ec0129ed9 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -18,6 +18,7 @@ package android.hardware.radio; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -159,12 +160,17 @@ public class RadioManager { /** * Forces the analog playback for the supporting radio technology. * - * User may disable digital playback for FM HD Radio or hybrid FM/DAB with - * this option. This is purely user choice, ie. does not reflect digital- + * <p>User may disable digital playback for FM HD Radio or hybrid FM/DAB with + * this option. This is purely user choice, i.e. does not reflect digital- * analog handover state managed from the HAL implementation side. * - * Some radio technologies may not support this, ie. DAB. + * <p>Some radio technologies may not support this, i.e. DAB. + * + * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM} + * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet} + * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}. */ + @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; /** * Forces the digital playback for the supporting radio technology. @@ -199,6 +205,30 @@ public class RadioManager { /** Enables DAB-FM soft-linking (related content). */ public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; + /** + * Forces the FM analog playback for the supporting radio technology. + * + * <p>User may disable FM digital playback for FM HD Radio or hybrid FM/DAB + * with this option. This is purely user choice, i.e. does not reflect + * digital-analog handover state managed from the HAL implementation side. + * + * <p>Some radio technologies may not support this, i.e. DAB. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int CONFIG_FORCE_ANALOG_FM = 10; + + /** + * Forces the AM analog playback for the supporting radio technology. + * + * <p>User may disable FM digital playback for AM HD Radio or hybrid AM/DAB + * with this option. This is purely user choice, i.e. does not reflect + * digital-analog handover state managed from the HAL implementation side. + * + * <p>Some radio technologies may not support this, i.e. DAB. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final int CONFIG_FORCE_ANALOG_AM = 11; + /** @hide */ @IntDef(prefix = { "CONFIG_" }, value = { CONFIG_FORCE_MONO, @@ -210,6 +240,8 @@ public class RadioManager { CONFIG_DAB_FM_LINKING, CONFIG_DAB_DAB_SOFT_LINKING, CONFIG_DAB_FM_SOFT_LINKING, + CONFIG_FORCE_ANALOG_FM, + CONFIG_FORCE_ANALOG_AM, }) @Retention(RetentionPolicy.SOURCE) public @interface ConfigFlag {} @@ -1441,6 +1473,9 @@ public class RadioManager { private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3; private static final int FLAG_TUNED = 1 << 4; private static final int FLAG_STEREO = 1 << 5; + private static final int FLAG_SIGNAL_ACQUIRED = 1 << 6; + private static final int FLAG_HD_SIS_ACQUIRED = 1 << 7; + private static final int FLAG_HD_AUDIO_ACQUIRED = 1 << 8; @NonNull private final ProgramSelector mSelector; @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo; @@ -1595,7 +1630,7 @@ public class RadioManager { } /** - * {@code true} if radio stream is not playing, ie. due to bad reception + * {@code true} if radio stream is not playing, i.e. due to bad reception * conditions or buffering. In this state volume knob MAY be disabled to * prevent user increasing volume too much. * It does NOT mean the user has muted audio. @@ -1621,6 +1656,28 @@ public class RadioManager { } /** + * @return {@code true} if the signal has been acquired. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public boolean isSignalAcquired() { + return (mInfoFlags & FLAG_SIGNAL_ACQUIRED) != 0; + } + /** + * @return {@code true} if HD Station Information Service (SIS) information is available. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public boolean isHdSisAvailable() { + return (mInfoFlags & FLAG_HD_SIS_ACQUIRED) != 0; + } + /** + * @return {@code true} if HD audio is available. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public boolean isHdAudioAvailable() { + return (mInfoFlags & FLAG_HD_AUDIO_ACQUIRED) != 0; + } + + /** * Signal quality (as opposed to the name) indication from 0 (no signal) * to 100 (excellent) * @return the signal quality indication. diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java index b7bf783754f7..db14c08b3698 100644 --- a/core/java/android/hardware/radio/RadioMetadata.java +++ b/core/java/android/hardware/radio/RadioMetadata.java @@ -15,6 +15,7 @@ */ package android.hardware.radio; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -30,6 +31,7 @@ import android.util.SparseArray; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -142,12 +144,84 @@ public final class RadioMetadata implements Parcelable { public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT"; + /** + * Short context description of comment + * + * <p>Comment could relate to the current audio program content, or it might + * be unrelated information that the station chooses to send. It is composed + * of short content description and actual text (see NRSC-G200-A and id3v2.3.0 + * for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = + "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION"; + + /** + * Actual text of comment + * + * @see #METADATA_KEY_COMMENT_SHORT_DESCRIPTION + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = + "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT"; + + /** + * Commercial + * + * <p>Commercial is application specific and generally used to facilitate the + * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_COMMERCIAL = + "android.hardware.radio.metadata.COMMERCIAL"; + + /** + * Array of Unique File Identifiers + * + * <p>Unique File Identifier (UFID) can be used to transmit an alphanumeric + * identifier of the current content, or of an advertised product or + * service (see NRSC-G200-A and id3v2.3.0 for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS"; + + /** + * HD short station name or HD universal short station name + * + * <p>It can be up to 12 characters (see SY_IDD_1020s for more info). + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_HD_STATION_NAME_SHORT = + "android.hardware.radio.metadata.HD_STATION_NAME_SHORT"; + + /** + * HD long station name, HD station slogan or HD station message + * + * <p>(see SY_IDD_1020s for more info) + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_HD_STATION_NAME_LONG = + "android.hardware.radio.metadata.HD_STATION_NAME_LONG"; + + /** + * Bit mask of all HD Radio subchannels available + * + * <p>Bit {@link ProgramSelector#SUB_CHANNEL_HD_1} from LSB represents the + * availability of HD-1 subchannel (main program service, MPS). Bits + * {@link ProgramSelector#SUB_CHANNEL_HD_2} to {@link ProgramSelector#SUB_CHANNEL_HD_8} + * from LSB represent HD-2 to HD-8 subchannel (supplemental program services, SPS) + * respectively. + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = + "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE"; private static final int METADATA_TYPE_INVALID = -1; private static final int METADATA_TYPE_INT = 0; private static final int METADATA_TYPE_TEXT = 1; private static final int METADATA_TYPE_BITMAP = 2; private static final int METADATA_TYPE_CLOCK = 3; + private static final int METADATA_TYPE_TEXT_ARRAY = 4; private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE; @@ -172,6 +246,13 @@ public final class RadioMetadata implements Parcelable { METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY); + METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT); } // keep in sync with: system/media/radio/include/system/radio_metadata.h @@ -288,9 +369,12 @@ public final class RadioMetadata implements Parcelable { return false; } for (String key : mBundle.keySet()) { - // This logic will return a false negative if we ever put Bundles into mBundle. As of - // 2019-04-09, we only put ints, Strings, and Parcelables in, so it's fine for now. - if (!mBundle.get(key).equals(otherBundle.get(key))) { + if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), + METADATA_TYPE_TEXT_ARRAY)) { + if (!Arrays.equals(mBundle.getStringArray(key), otherBundle.getStringArray(key))) { + return false; + } + } else if (!Objects.equals(mBundle.get(key), otherBundle.get(key))) { return false; } } @@ -326,7 +410,21 @@ public final class RadioMetadata implements Parcelable { sb.append(keyDisp); sb.append('='); - sb.append(mBundle.get(key)); + if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key), + METADATA_TYPE_TEXT_ARRAY)) { + String[] stringArrayValue = mBundle.getStringArray(key); + sb.append('['); + for (int i = 0; i < stringArrayValue.length; i++) { + if (i != 0) { + sb.append(','); + } + sb.append(stringArrayValue[i]); + } + sb.append(']'); + } else { + sb.append(mBundle.get(key)); + } + } sb.append("]"); @@ -427,6 +525,36 @@ public final class RadioMetadata implements Parcelable { return clock; } + /** + * Gets the string array value associated with the given key as a string + * array. + * + * <p>Only string array keys may be used with this method: + * <ul> + * <li>{@link #METADATA_KEY_UFIDS}</li> + * </ul> + * + * @param key The key the value is stored under + * @return String array of the given string-array-type key + * @throws NullPointerException if metadata key is {@code null} + * @throws IllegalArgumentException if the metadata with the key is not found in + * metadata or the key is not of string-array type + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + @NonNull + public String[] getStringArray(@NonNull String key) { + Objects.requireNonNull(key, "Metadata key can not be null"); + if (!Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { + throw new IllegalArgumentException("Failed to retrieve key " + key + + " as string array"); + } + String[] stringArrayValue = mBundle.getStringArray(key); + if (stringArrayValue == null) { + throw new IllegalArgumentException("Key " + key + " is not found in metadata"); + } + return stringArrayValue; + } + @Override public int describeContents() { return 0; @@ -539,6 +667,11 @@ public final class RadioMetadata implements Parcelable { * <li>{@link #METADATA_KEY_ARTIST}</li> * <li>{@link #METADATA_KEY_ALBUM}</li> * <li>{@link #METADATA_KEY_GENRE}</li> + * <li>{@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}</li> + * <li>{@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}</li> + * <li>{@link #METADATA_KEY_COMMERCIAL}</li> + * <li>{@link #METADATA_KEY_HD_STATION_NAME_SHORT}</li> + * <li>{@link #METADATA_KEY_HD_STATION_NAME_LONG}</li> * </ul> * * @param key The key for referencing this value @@ -563,6 +696,7 @@ public final class RadioMetadata implements Parcelable { * <li>{@link #METADATA_KEY_RDS_PI}</li> * <li>{@link #METADATA_KEY_RDS_PTY}</li> * <li>{@link #METADATA_KEY_RBDS_PTY}</li> + * <li>{@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}</li> * </ul> * or any bitmap represented by its identifier. * @@ -621,6 +755,35 @@ public final class RadioMetadata implements Parcelable { } /** + * Put a String array into the meta data. Custom keys may be used, but if + * the METADATA_KEYs defined in this class are used they may only be one + * of the following: + * <ul> + * <li>{@link #METADATA_KEY_UFIDS}</li> + * </ul> + * + * @param key The key for referencing this value + * @param value The String value to store + * @return the same Builder instance + * @throws NullPointerException if key or value is null + * @throws IllegalArgumentException if the key is not string-array-type key + */ + @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED) + @NonNull + public Builder putStringArray(@NonNull String key, @NonNull String[] value) { + Objects.requireNonNull(key, "Key can not be null"); + Objects.requireNonNull(value, "Value can not be null"); + if (!METADATA_KEYS_TYPE.containsKey(key) + || !Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) { + throw new IllegalArgumentException("The " + key + + " key cannot be used to put a RadioMetadata String Array."); + } + mBundle.putStringArray(key, value); + return this; + } + + + /** * Creates a {@link RadioMetadata} instance with the specified fields. * * @return a new {@link RadioMetadata} object diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index bdbca91a715a..ba31ca3627bf 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -363,7 +363,7 @@ final class TunerAdapter extends RadioTuner { @Override public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) { try { - return mTuner.isConfigFlagSet(flag); + return mTuner.isConfigFlagSet(convertForceAnalogConfigFlag(flag)); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } @@ -372,7 +372,7 @@ final class TunerAdapter extends RadioTuner { @Override public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) { try { - mTuner.setConfigFlag(flag, value); + mTuner.setConfigFlag(convertForceAnalogConfigFlag(flag), value); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } @@ -411,4 +411,13 @@ final class TunerAdapter extends RadioTuner { return false; } } + + private @RadioManager.ConfigFlag int convertForceAnalogConfigFlag( + @RadioManager.ConfigFlag int flag) throws RemoteException { + if (Flags.hdRadioImproved() && flag == RadioManager.CONFIG_FORCE_ANALOG + && mTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) { + flag = RadioManager.CONFIG_FORCE_ANALOG_FM; + } + return flag; + } } diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp index 054d10c336e6..6be553b99c3c 100644 --- a/core/tests/BroadcastRadioTests/Android.bp +++ b/core/tests/BroadcastRadioTests/Android.bp @@ -40,6 +40,8 @@ android_test { "androidx.test.rules", "truth", "testng", + "android.hardware.radio.flags-aconfig-java", + "flag-junit", "mockito-target-extended", ], libs: ["android.test.base"], diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java index d638fedc5358..d4a88c49b38f 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java @@ -16,8 +16,6 @@ package android.hardware.radio; -import static com.google.common.truth.Truth.assertWithMessage; - import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -36,8 +34,14 @@ import android.content.pm.ApplicationInfo; import android.os.Build; import android.os.Parcel; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; +import com.google.common.truth.Expect; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -143,12 +147,17 @@ public final class ProgramListTest { @Mock private RadioTuner.Callback mTunerCallbackMock; + @Rule + public final Expect mExpect = Expect.create(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void getIdentifierTypes_forFilter() { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes()) + mExpect.withMessage("Filtered identifier types").that(filter.getIdentifierTypes()) .containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES); } @@ -157,7 +166,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filtered identifiers").that(filter.getIdentifiers()) + mExpect.withMessage("Filtered identifiers").that(filter.getIdentifiers()) .containsExactlyElementsIn(FILTER_IDENTIFIERS); } @@ -166,7 +175,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filter including categories") + mExpect.withMessage("Filter including categories") .that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES); } @@ -175,7 +184,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filter excluding modifications") + mExpect.withMessage("Filter excluding modifications") .that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS); } @@ -184,7 +193,7 @@ public final class ProgramListTest { ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Filter vendor obtained from filter without vendor filter") + mExpect.withMessage("Filter vendor obtained from filter without vendor filter") .that(filter.getVendorFilter()).isNull(); } @@ -192,13 +201,13 @@ public final class ProgramListTest { public void getVendorFilter_forFilterWithVendorFilter() { ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER); - assertWithMessage("Filter vendor obtained from filter with vendor filter") + mExpect.withMessage("Filter vendor obtained from filter with vendor filter") .that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER); } @Test public void describeContents_forFilter() { - assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0); + mExpect.withMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0); } @Test @@ -206,7 +215,7 @@ public final class ProgramListTest { ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS); - assertWithMessage("Hash code of the same filter") + mExpect.withMessage("Hash code of the same filter") .that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode()); } @@ -214,7 +223,7 @@ public final class ProgramListTest { public void hashCode_withDifferentFilters_notEquals() { ProgramList.Filter filterCompared = new ProgramList.Filter(); - assertWithMessage("Hash code of the different filter") + mExpect.withMessage("Hash code of the different filter") .that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode()); } @@ -227,7 +236,7 @@ public final class ProgramListTest { ProgramList.Filter filterFromParcel = ProgramList.Filter.CREATOR.createFromParcel(parcel); - assertWithMessage("Filter created from parcel") + mExpect.withMessage("Filter created from parcel") .that(filterFromParcel).isEqualTo(TEST_FILTER); } @@ -235,36 +244,37 @@ public final class ProgramListTest { public void newArray_forFilterCreator() { ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE); - assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE); + mExpect.withMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE); } @Test public void isPurge_forChunk() { - assertWithMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE); + mExpect.withMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE); } @Test public void isComplete_forChunk() { - assertWithMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete()) + mExpect.withMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete()) .isEqualTo(IS_COMPLETE); } @Test public void getModified_forChunk() { - assertWithMessage("Modified program info in chunk") + mExpect.withMessage("Modified program info in chunk") .that(FM_DAB_ADD_CHUNK.getModified()) .containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); } @Test public void getRemoved_forChunk() { - assertWithMessage("Removed program identifiers in chunk") + mExpect.withMessage("Removed program identifiers in chunk") .that(FM_DAB_ADD_CHUNK.getRemoved()).containsExactly(RDS_UNIQUE_IDENTIFIER); } @Test public void describeContents_forChunk() { - assertWithMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents()).isEqualTo(0); + mExpect.withMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents()) + .isEqualTo(0); } @Test @@ -276,7 +286,7 @@ public final class ProgramListTest { ProgramList.Chunk chunkFromParcel = ProgramList.Chunk.CREATOR.createFromParcel(parcel); - assertWithMessage("Chunk created from parcel") + mExpect.withMessage("Chunk created from parcel") .that(chunkFromParcel).isEqualTo(FM_DAB_ADD_CHUNK); } @@ -284,7 +294,7 @@ public final class ProgramListTest { public void newArray_forChunkCreator() { ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE); - assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE); + mExpect.withMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE); } @Test @@ -295,7 +305,7 @@ public final class ProgramListTest { IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> mRadioTuner.getProgramList(parameters)); - assertWithMessage("Exception for getting program list when not ready") + mExpect.withMessage("Exception for getting program list when not ready") .that(thrown).hasMessageThat().contains("Program list is not ready yet"); } @@ -308,7 +318,7 @@ public final class ProgramListTest { RuntimeException thrown = assertThrows(RuntimeException.class, () -> mRadioTuner.getProgramList(parameters)); - assertWithMessage("Exception for getting program list when service is dead") + mExpect.withMessage("Exception for getting program list when service is dead") .that(thrown).hasMessageThat().contains("Service died"); } @@ -330,7 +340,7 @@ public final class ProgramListTest { ProgramList nullProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); - assertWithMessage("Exception for radio HAL client not supporting program list") + mExpect.withMessage("Exception for radio HAL client not supporting program list") .that(nullProgramList).isNull(); } @@ -344,7 +354,7 @@ public final class ProgramListTest { mRadioTuner.getDynamicProgramList(TEST_FILTER); }); - assertWithMessage("Exception for radio HAL client service died") + mExpect.withMessage("Exception for radio HAL client service died") .that(thrown).hasMessageThat().contains("Service died"); } @@ -360,7 +370,7 @@ public final class ProgramListTest { verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER); verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete(); - assertWithMessage("Program info in program list after adding FM and DAB info") + mExpect.withMessage("Program info in program list after adding FM and DAB info") .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); } @@ -378,7 +388,7 @@ public final class ProgramListTest { mTunerCallback.onProgramListUpdated(fmRemovedChunk); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER); - assertWithMessage("Program info in program list after removing FM id") + mExpect.withMessage("Program info in program list after removing FM id") .that(mProgramList.toList()).containsExactly(DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); } @@ -397,7 +407,7 @@ public final class ProgramListTest { verify(mListCallbackMocks[0], after(TIMEOUT_MS).never()).onItemRemoved( DAB_DMB_SID_EXT_IDENTIFIER); - assertWithMessage("Program info in program list after removing part of DAB ids") + mExpect.withMessage("Program info in program list after removing part of DAB ids") .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_2); } @@ -419,7 +429,7 @@ public final class ProgramListTest { mTunerCallback.onProgramListUpdated(dabRemovedChunk2); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER); - assertWithMessage("Program info in program list after removing all DAB ids") + mExpect.withMessage("Program info in program list after removing all DAB ids") .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO); } @@ -448,7 +458,7 @@ public final class ProgramListTest { verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER); verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER); - assertWithMessage("Program list after purge chunk applied") + mExpect.withMessage("Program list after purge chunk applied") .that(mProgramList.toList()).isEmpty(); } @@ -607,6 +617,49 @@ public final class ProgramListTest { verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates(); } + @Test + public void get() throws Exception { + createRadioTuner(); + mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); + registerListCallbacks(/* numCallbacks= */ 1); + mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); + + mExpect.withMessage( + "FM program info in program list after updating with chunk of FM program") + .that(mProgramList.get(FM_IDENTIFIER)).isEqualTo(FM_PROGRAM_INFO); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getProgramInfos() throws Exception { + createRadioTuner(); + mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); + registerListCallbacks(/* numCallbacks= */ 1); + mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER); + + mExpect.withMessage("FM program info in program list") + .that(mProgramList.getProgramInfos(FM_IDENTIFIER)).containsExactly(FM_PROGRAM_INFO); + mExpect.withMessage("All DAB program info in program list") + .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER)) + .containsExactly(DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getProgramInfos_withIdNotFound() throws Exception { + createRadioTuner(); + mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER); + registerListCallbacks(/* numCallbacks= */ 1); + mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK); + verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER); + + mExpect.withMessage("DAB program info in program list") + .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER)).isEmpty(); + } + private static ProgramSelector createProgramSelector(int programType, ProgramSelector.Identifier identifier) { return new ProgramSelector(programType, identifier, /* secondaryIds= */ null, diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java index b9f4c3fa0a77..03de1430fec8 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java @@ -32,8 +32,12 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Parcel; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArrayMap; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -42,6 +46,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -90,10 +95,26 @@ public final class RadioManagerTest { private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties(/* dabFrequencyTable= */ null); + private static final int DAB_INFO_FLAG_LIVE_VALUE = 1; + private static final int DAB_INFO_FLAG_TUNED_VALUE = 1 << 4; + private static final int DAB_INFO_FLAG_STEREO_VALUE = 1 << 5; + private static final int HD_INFO_FLAG_LIVE_VALUE = 1; + private static final int HD_INFO_FLAG_TUNED_VALUE = 1 << 4; + private static final int HD_INFO_FLAG_STEREO_VALUE = 1 << 5; + private static final int HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE = 1 << 6; + private static final int HD_INFO_FLAG_SIS_ACQUISITION_VALUE = 1 << 7; /** - * Info flags with live, tuned and stereo enabled + * Info flags with live, tuned, and stereo enabled for DAB program */ - private static final int INFO_FLAGS = 0b110001; + private static final int INFO_FLAGS_DAB = DAB_INFO_FLAG_LIVE_VALUE | DAB_INFO_FLAG_TUNED_VALUE + | DAB_INFO_FLAG_STEREO_VALUE; + /** + * HD program info flags with live, tuned, stereo enabled, signal acquired, SIS information + * available but audio unavailable + */ + private static final int INFO_FLAGS_HD = HD_INFO_FLAG_LIVE_VALUE | HD_INFO_FLAG_TUNED_VALUE + | HD_INFO_FLAG_STEREO_VALUE | HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE + | HD_INFO_FLAG_SIS_ACQUISITION_VALUE; private static final int SIGNAL_QUALITY = 2; private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER = new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, @@ -112,9 +133,20 @@ public final class RadioManagerTest { new ProgramSelector.Identifier[]{ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null); + + private static final long HD_FREQUENCY = 97_100; + private static final ProgramSelector.Identifier HD_STATION_EXT_IDENTIFIER = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, + /* value= */ (HD_FREQUENCY << 36) | 0x1L); + private static final ProgramSelector HD_SELECTOR = new ProgramSelector( + ProgramSelector.PROGRAM_TYPE_FM_HD, HD_STATION_EXT_IDENTIFIER, + new ProgramSelector.Identifier[]{}, /* vendorIds= */ null); + private static final RadioMetadata METADATA = createMetadata(); private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO = createDabProgramInfo(DAB_SELECTOR); + private static final RadioManager.ProgramInfo HD_PROGRAM_INFO = createHdProgramInfo( + HD_SELECTOR); private static final int EVENT_ANNOUNCEMENT_TYPE = Announcement.TYPE_EVENT; private static final List<Announcement> TEST_ANNOUNCEMENT_LIST = Arrays.asList( @@ -135,6 +167,9 @@ public final class RadioManagerTest { @Mock private ICloseHandle mCloseHandleMock; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void getType_forBandDescriptor() { RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); @@ -927,6 +962,27 @@ public final class RadioManagerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isSignalAcquired_forProgramInfo() { + assertWithMessage("Signal acquisition status for HD program info") + .that(HD_PROGRAM_INFO.isSignalAcquired()).isTrue(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isHdSisAvailable_forProgramInfo() { + assertWithMessage("SIS information acquisition status for HD program") + .that(HD_PROGRAM_INFO.isHdSisAvailable()).isTrue(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isHdAudioAvailable_forProgramInfo() { + assertWithMessage("Audio acquisition status for HD program") + .that(HD_PROGRAM_INFO.isHdAudioAvailable()).isFalse(); + } + + @Test public void getSignalStrength_forProgramInfo() { assertWithMessage("Signal strength of DAB program info") .that(DAB_PROGRAM_INFO.getSignalStrength()).isEqualTo(SIGNAL_QUALITY); @@ -1156,9 +1212,18 @@ public final class RadioManagerTest { } private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) { - return new RadioManager.ProgramInfo(selector, DAB_SID_EXT_IDENTIFIER, - DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS, - SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null); + return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), + DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), + INFO_FLAGS_DAB, SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null); + } + + private static RadioManager.ProgramInfo createHdProgramInfo(ProgramSelector selector) { + long frequency = (selector.getPrimaryId().getValue() >> 32); + ProgramSelector.Identifier physicallyTunedToId = new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, frequency); + return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), physicallyTunedToId, + Collections.emptyList(), INFO_FLAGS_HD, SIGNAL_QUALITY, METADATA, + /* vendorInfo= */ null); } private void createRadioManager() throws RemoteException { diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java index e348a51f6214..3891accbba44 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java @@ -22,12 +22,18 @@ import static org.junit.Assert.assertThrows; import android.graphics.Bitmap; import android.os.Parcel; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Arrays; import java.util.Set; @RunWith(MockitoJUnitRunner.class) @@ -35,6 +41,8 @@ public final class RadioMetadataTest { private static final int CREATOR_ARRAY_SIZE = 3; private static final int INT_KEY_VALUE = 1; + private static final String ARTIST_KEY_VALUE = "artistTest"; + private static final String[] UFIDS_VALUE = new String[]{"ufid1", "ufid2"}; private static final long TEST_UTC_SECOND_SINCE_EPOCH = 200; private static final int TEST_TIME_ZONE_OFFSET_MINUTES = 1; @@ -43,6 +51,9 @@ public final class RadioMetadataTest { @Mock private Bitmap mBitmapValue; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void describeContents_forClock() { RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH, @@ -97,7 +108,7 @@ public final class RadioMetadataTest { mBuilder.putInt(invalidIntKey, INT_KEY_VALUE); }); - assertWithMessage("Exception for putting illegal int-value key %s", invalidIntKey) + assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey) .that(thrown).hasMessageThat() .matches(".*" + invalidIntKey + ".*cannot.*int.*?"); } @@ -117,6 +128,42 @@ public final class RadioMetadataTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void putStringArray_withIllegalKey_throwsException() { + String invalidStringArrayKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE); + }); + + assertWithMessage("Exception for putting illegal string-array-value for key %s", + invalidStringArrayKey).that(thrown).hasMessageThat() + .matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void putStringArray_withNullKey_throwsException() { + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE); + }); + + assertWithMessage("Exception for putting string-array with null key") + .that(thrown).hasMessageThat().contains("can not be null"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void putStringArray_withNullString_throwsException() { + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null); + }); + + assertWithMessage("Exception for putting null string-array") + .that(thrown).hasMessageThat().contains("can not be null"); + } + + @Test public void containsKey_withKeyInMetadata() { String key = RadioMetadata.METADATA_KEY_RDS_PI; RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build(); @@ -156,11 +203,10 @@ public final class RadioMetadataTest { @Test public void getString_withKeyInMetadata() { String key = RadioMetadata.METADATA_KEY_ARTIST; - String value = "artistTest"; - RadioMetadata metadata = mBuilder.putString(key, value).build(); + RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build(); assertWithMessage("String value for key %s in metadata", key) - .that(metadata.getString(key)).isEqualTo(value); + .that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE); } @Test @@ -235,10 +281,62 @@ public final class RadioMetadataTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withKeyInMetadata() { + String key = RadioMetadata.METADATA_KEY_UFIDS; + RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build(); + + assertWithMessage("String-array value for key %s not in metadata", key) + .that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withKeyNotInMetadata() { + String key = RadioMetadata.METADATA_KEY_UFIDS; + RadioMetadata metadata = mBuilder.build(); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + metadata.getStringArray(key); + }); + + assertWithMessage("Exception for getting string array for string-array value for key %s " + + "not in metadata", key).that(thrown).hasMessageThat().contains("not found"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withNullKey() { + RadioMetadata metadata = mBuilder.build(); + + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + metadata.getStringArray(/* key= */ null); + }); + + assertWithMessage("Exception for getting string array with null key") + .that(thrown).hasMessageThat().contains("can not be null"); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void getStringArray_withInvalidKey() { + String invalidClockKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG; + RadioMetadata metadata = mBuilder.build(); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + metadata.getStringArray(invalidClockKey); + }); + + assertWithMessage("Exception for getting string array for key %s not of string-array type", + invalidClockKey).that(thrown).hasMessageThat() + .contains("string array"); + } + + @Test public void size_withNonEmptyMetadata() { RadioMetadata metadata = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) .build(); assertWithMessage("Size of fields in non-empty metadata") @@ -257,7 +355,7 @@ public final class RadioMetadataTest { public void keySet_withNonEmptyMetadata() { RadioMetadata metadata = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) .putBitmap(RadioMetadata.METADATA_KEY_ICON, mBitmapValue) .build(); @@ -291,7 +389,7 @@ public final class RadioMetadataTest { public void equals_forMetadataWithSameContents_returnsTrue() { RadioMetadata metadata = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) .build(); RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata); RadioMetadata metadataCopied = copyBuilder.build(); @@ -315,10 +413,29 @@ public final class RadioMetadataTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) public void writeToParcel_forRadioMetadata() { RadioMetadata metadataExpected = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) - .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) + .build(); + Parcel parcel = Parcel.obtain(); + + metadataExpected.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel); + assertWithMessage("Radio metadata created from parcel") + .that(metadataFromParcel).isEqualTo(metadataExpected); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void writeToParcel_forRadioMetadata_withStringArrayTypeMetadata() { + RadioMetadata metadataExpected = mBuilder + .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) + .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE) + .putStringArray(RadioMetadata.METADATA_KEY_UFIDS, UFIDS_VALUE) .build(); Parcel parcel = Parcel.obtain(); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java index 6a6a951c94c8..7ca806b49b68 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.after; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,9 +36,14 @@ import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; import android.os.Build; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -77,6 +83,9 @@ public final class TunerAdapterTest { @Mock private RadioTuner.Callback mCallbackMock; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() throws Exception { mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION; @@ -604,6 +613,44 @@ public final class TunerAdapterTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogSupported() + throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)) + .thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); + + assertWithMessage("Force analog with feature flag enabled and force FM supported") + .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isTrue(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogNotSupported() + throws Exception { + when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) + .thenReturn(false); + when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); + + assertWithMessage("Force analog with feature flag enabled but force FM unsupported") + .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse(); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void isConfigFlagSet_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() + throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false); + + assertWithMessage("Force analog without Force FM enabled") + .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse(); + } + + @Test public void isConfigFlagSet_whenServiceDied_fails() throws Exception { when(mTunerMock.isConfigFlagSet(anyInt())).thenThrow(new RemoteException()); @@ -636,6 +683,43 @@ public final class TunerAdapterTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void setConfigFlag_withForceAnalogWhenFmForceAnalogSupported() throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + + mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); + + verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG), + anyBoolean()); + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG_FM, false); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void setConfigFlag_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) + .thenReturn(false); + + mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); + + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false); + verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG_FM), + anyBoolean()); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED) + public void setConfigFlag_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled() + throws Exception { + when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true); + + mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false); + + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false); + } + + @Test public void getParameters_forTunerAdapter() throws Exception { List<String> parameterKeys = List.of("ParameterKeyMock"); Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock"); |