summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt34
-rw-r--r--core/java/android/hardware/radio/ProgramList.java57
-rw-r--r--core/java/android/hardware/radio/ProgramSelector.java146
-rw-r--r--core/java/android/hardware/radio/RadioManager.java65
-rw-r--r--core/java/android/hardware/radio/RadioMetadata.java171
-rw-r--r--core/java/android/hardware/radio/TunerAdapter.java13
-rw-r--r--core/tests/BroadcastRadioTests/Android.bp2
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java111
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java75
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java133
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java84
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");