Merge "Implement ProgramSelector for font-end Java APIs."
diff --git a/Android.mk b/Android.mk
index 2d8739b..cca87ca 100644
--- a/Android.mk
+++ b/Android.mk
@@ -712,6 +712,7 @@
frameworks/base/core/java/android/print/PrinterInfo.aidl \
frameworks/base/core/java/android/print/PrintJobId.aidl \
frameworks/base/core/java/android/printservice/recommendation/RecommendationInfo.aidl \
+ frameworks/base/core/java/android/hardware/radio/ProgramSelector.aidl \
frameworks/base/core/java/android/hardware/radio/RadioManager.aidl \
frameworks/base/core/java/android/hardware/radio/RadioMetadata.aidl \
frameworks/base/core/java/android/hardware/usb/UsbDevice.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index 678a4cd..d176e39 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -17215,6 +17215,63 @@
package android.hardware.radio {
+ public final class ProgramSelector implements android.os.Parcelable {
+ ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]);
+ method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int);
+ method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int, int);
+ method public int describeContents();
+ method public android.hardware.radio.ProgramSelector.Identifier[] getAllIds(int);
+ method public long getFirstId(int);
+ method public android.hardware.radio.ProgramSelector.Identifier getPrimaryId();
+ method public int getProgramType();
+ method public android.hardware.radio.ProgramSelector.Identifier[] getSecondaryIds();
+ method public long[] getVendorIds();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector> CREATOR;
+ field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1
+ field public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; // 0x6
+ field public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; // 0x8
+ field public static final int IDENTIFIER_TYPE_DAB_SCID = 7; // 0x7
+ field public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
+ field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
+ 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 public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
+ field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
+ field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 12; // 0xc
+ field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 11; // 0xb
+ field public static final int IDENTIFIER_TYPE_VENDOR1_PRIMARY = 13; // 0xd
+ field public static final int IDENTIFIER_TYPE_VENDOR2_PRIMARY = 14; // 0xe
+ field public static final int IDENTIFIER_TYPE_VENDOR3_PRIMARY = 15; // 0xf
+ field public static final int IDENTIFIER_TYPE_VENDOR4_PRIMARY = 16; // 0x10
+ field public static final int PROGRAM_TYPE_AM = 1; // 0x1
+ field public static final int PROGRAM_TYPE_AM_HD = 3; // 0x3
+ field public static final int PROGRAM_TYPE_DAB = 5; // 0x5
+ field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6
+ field public static final int PROGRAM_TYPE_FM = 2; // 0x2
+ field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4
+ field public static final int PROGRAM_TYPE_SXM = 7; // 0x7
+ field public static final int PROGRAM_TYPE_VENDOR1 = 8; // 0x8
+ field public static final int PROGRAM_TYPE_VENDOR2 = 9; // 0x9
+ field public static final int PROGRAM_TYPE_VENDOR3 = 10; // 0xa
+ field public static final int PROGRAM_TYPE_VENDOR4 = 11; // 0xb
+ }
+
+ public static final class ProgramSelector.Identifier implements android.os.Parcelable {
+ ctor public ProgramSelector.Identifier(int, long);
+ method public int describeContents();
+ method public int getType();
+ method public long getValue();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
+ }
+
+ public static abstract class ProgramSelector.IdentifierType implements java.lang.annotation.Annotation {
+ }
+
+ public static abstract class ProgramSelector.ProgramType implements java.lang.annotation.Annotation {
+ }
+
public class RadioManager {
method public int listModules(java.util.List<android.hardware.radio.RadioManager.ModuleProperties>);
method public android.hardware.radio.RadioTuner openTuner(int, android.hardware.radio.RadioManager.BandConfig, boolean, android.hardware.radio.RadioTuner.Callback, android.os.Handler);
@@ -17222,6 +17279,7 @@
field public static final int BAND_AM_HD = 3; // 0x3
field public static final int BAND_FM = 1; // 0x1
field public static final int BAND_FM_HD = 2; // 0x2
+ field public static final int BAND_INVALID = -1; // 0xffffffff
field public static final int CLASS_AM_FM = 0; // 0x0
field public static final int CLASS_DT = 2; // 0x2
field public static final int CLASS_SAT = 1; // 0x1
@@ -17257,6 +17315,9 @@
field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.AmBandDescriptor> CREATOR;
}
+ public static abstract class RadioManager.Band implements java.lang.annotation.Annotation {
+ }
+
public static class RadioManager.BandConfig implements android.os.Parcelable {
method public int describeContents();
method public int getLowerLimit();
@@ -17331,10 +17392,11 @@
public static class RadioManager.ProgramInfo implements android.os.Parcelable {
method public int describeContents();
- method public int getChannel();
+ method public deprecated int getChannel();
method public android.hardware.radio.RadioMetadata getMetadata();
+ method public android.hardware.radio.ProgramSelector getSelector();
method public int getSignalStrength();
- method public int getSubChannel();
+ method public deprecated int getSubChannel();
method public java.lang.String getVendorExension();
method public boolean isDigital();
method public boolean isLive();
@@ -17406,7 +17468,8 @@
method public abstract int setMute(boolean);
method public abstract boolean startBackgroundScan();
method public abstract int step(int, boolean);
- method public abstract int tune(int, int);
+ method public abstract deprecated int tune(int, int);
+ method public abstract void tune(android.hardware.radio.ProgramSelector);
field public static final int DIRECTION_DOWN = 1; // 0x1
field public static final int DIRECTION_UP = 0; // 0x0
field public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index 69e135d..2932c3e 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -16,6 +16,7 @@
package android.hardware.radio;
+import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
/** {@hide} */
@@ -52,7 +53,7 @@
* @throws IllegalArgumentException if invalid arguments are passed
* @throws IllegalStateException if called out of sequence
*/
- void tune(int channel, int subChannel);
+ void tune(in ProgramSelector selector);
/**
* @throws IllegalStateException if called out of sequence
diff --git a/core/java/android/hardware/radio/ProgramSelector.aidl b/core/java/android/hardware/radio/ProgramSelector.aidl
new file mode 100644
index 0000000..545269a
--- /dev/null
+++ b/core/java/android/hardware/radio/ProgramSelector.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+/** @hide */
+parcelable ProgramSelector;
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
new file mode 100644
index 0000000..4638dd5
--- /dev/null
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -0,0 +1,506 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * A set of identifiers necessary to tune to a given station.
+ *
+ * This can hold various identifiers, like
+ * - AM/FM frequency
+ * - HD Radio subchannel
+ * - DAB channel info
+ *
+ * The primary ID uniquely identifies a station and can be used for equality
+ * check. The secondary IDs are supplementary and can speed up tuning process,
+ * but the primary ID is sufficient (ie. after a full band scan).
+ *
+ * Two selectors with different secondary IDs, but the same primary ID are
+ * considered equal. In particular, secondary IDs vector may get updated for
+ * an entry on the program list (ie. when a better frequency for a given
+ * station is found).
+ *
+ * The primaryId of a given programType MUST be of a specific type:
+ * - AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;
+ * - AM_HD, FM_HD: HD_STATION_ID_EXT;
+ * - DAB: DAB_SIDECC;
+ * - DRMO: DRMO_SERVICE_ID;
+ * - SXM: SXM_SERVICE_ID;
+ * - VENDOR: VENDOR_PRIMARY.
+ * @hide
+ */
+@SystemApi
+public final class ProgramSelector implements Parcelable {
+ /** Analogue AM radio (with or without RDS). */
+ public static final int PROGRAM_TYPE_AM = 1;
+ /** analogue FM radio (with or without RDS). */
+ public static final int PROGRAM_TYPE_FM = 2;
+ /** AM HD Radio. */
+ public static final int PROGRAM_TYPE_AM_HD = 3;
+ /** FM HD Radio. */
+ public static final int PROGRAM_TYPE_FM_HD = 4;
+ /** Digital audio broadcasting. */
+ public static final int PROGRAM_TYPE_DAB = 5;
+ /** Digital Radio Mondiale. */
+ public static final int PROGRAM_TYPE_DRMO = 6;
+ /** SiriusXM Satellite Radio. */
+ public static final int PROGRAM_TYPE_SXM = 7;
+ /** Vendor-specific, not synced across devices. */
+ public static final int PROGRAM_TYPE_VENDOR1 = 8;
+ public static final int PROGRAM_TYPE_VENDOR2 = 9;
+ public static final int PROGRAM_TYPE_VENDOR3 = 10;
+ public static final int PROGRAM_TYPE_VENDOR4 = 11;
+ @IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
+ PROGRAM_TYPE_AM,
+ PROGRAM_TYPE_FM,
+ PROGRAM_TYPE_AM_HD,
+ PROGRAM_TYPE_FM_HD,
+ PROGRAM_TYPE_DAB,
+ PROGRAM_TYPE_DRMO,
+ PROGRAM_TYPE_SXM,
+ PROGRAM_TYPE_VENDOR1,
+ PROGRAM_TYPE_VENDOR2,
+ PROGRAM_TYPE_VENDOR3,
+ PROGRAM_TYPE_VENDOR4,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProgramType {}
+
+ /** kHz */
+ public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
+ /** 16bit */
+ public static final int IDENTIFIER_TYPE_RDS_PI = 2;
+ /**
+ * 64bit compound primary identifier for HD Radio.
+ *
+ * Consists of (from the LSB):
+ * - 32bit: Station ID number;
+ * - 4bit: HD_SUBCHANNEL;
+ * - 18bit: AMFM_FREQUENCY.
+ * The remaining bits should be set to zeros when writing on the chip side
+ * and ignored when read.
+ */
+ public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
+ /**
+ * HD Radio subchannel - a value of range 0-7.
+ *
+ * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
+ * as opposed to HD Radio standard (where it's 1-based).
+ */
+ public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4;
+ /**
+ * 24bit compound primary identifier for DAB.
+ *
+ * Consists of (from the LSB):
+ * - 16bit: SId;
+ * - 8bit: ECC code.
+ * The remaining bits should be set to zeros when writing on the chip side
+ * and ignored when read.
+ */
+ public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
+ /** 16bit */
+ public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
+ /** 12bit */
+ public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
+ /** kHz */
+ public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
+ /** 24bit */
+ public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
+ /** kHz */
+ public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
+ /** 32bit */
+ public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 11;
+ /** 0-999 range */
+ public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 12;
+ /**
+ * Primary identifier for vendor-specific radio technology.
+ * The value format is determined by a vendor.
+ *
+ * It must not be used in any other programType than VENDORx.
+ */
+ public static final int IDENTIFIER_TYPE_VENDOR1_PRIMARY = 13;
+ public static final int IDENTIFIER_TYPE_VENDOR2_PRIMARY = 14;
+ public static final int IDENTIFIER_TYPE_VENDOR3_PRIMARY = 15;
+ public static final int IDENTIFIER_TYPE_VENDOR4_PRIMARY = 16;
+ @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
+ IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ IDENTIFIER_TYPE_RDS_PI,
+ IDENTIFIER_TYPE_HD_STATION_ID_EXT,
+ IDENTIFIER_TYPE_HD_SUBCHANNEL,
+ IDENTIFIER_TYPE_DAB_SIDECC,
+ IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ IDENTIFIER_TYPE_DAB_SCID,
+ IDENTIFIER_TYPE_DAB_FREQUENCY,
+ IDENTIFIER_TYPE_DRMO_SERVICE_ID,
+ IDENTIFIER_TYPE_DRMO_FREQUENCY,
+ IDENTIFIER_TYPE_SXM_SERVICE_ID,
+ IDENTIFIER_TYPE_SXM_CHANNEL,
+ IDENTIFIER_TYPE_VENDOR1_PRIMARY,
+ IDENTIFIER_TYPE_VENDOR2_PRIMARY,
+ IDENTIFIER_TYPE_VENDOR3_PRIMARY,
+ IDENTIFIER_TYPE_VENDOR4_PRIMARY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IdentifierType {}
+
+ private final @ProgramType int mProgramType;
+ private final @NonNull Identifier mPrimaryId;
+ private final @NonNull Identifier[] mSecondaryIds;
+ private final @NonNull long[] mVendorIds;
+
+ /**
+ * Constructor for ProgramSelector.
+ *
+ * It's not desired to modify selector objects, so all its fields are initialized at creation.
+ *
+ * Identifier lists must not contain any nulls, but can itself be null to be interpreted
+ * as empty list at object creation.
+ *
+ * @param programType type of a radio technology.
+ * @param primaryId primary program identifier.
+ * @param secondaryIds list of secondary program identifiers.
+ * @param vendorIds list of vendor-specific program identifiers.
+ */
+ public ProgramSelector(@ProgramType int programType, @NonNull Identifier primaryId,
+ @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds) {
+ if (secondaryIds == null) secondaryIds = new Identifier[0];
+ if (vendorIds == null) vendorIds = new long[0];
+ if (Stream.of(secondaryIds).anyMatch(id -> id == null)) {
+ throw new IllegalArgumentException("secondaryIds list must not contain nulls");
+ }
+ mProgramType = programType;
+ mPrimaryId = Objects.requireNonNull(primaryId);
+ mSecondaryIds = secondaryIds;
+ mVendorIds = vendorIds;
+ }
+
+ /**
+ * Type of a radio technology.
+ *
+ * @returns program type.
+ */
+ public @ProgramType int getProgramType() {
+ return mProgramType;
+ }
+
+ /**
+ * Primary program identifier uniquely identifies a station and is used to
+ * determine equality between two ProgramSelectors.
+ *
+ * @returns primary identifier.
+ */
+ public @NonNull Identifier getPrimaryId() {
+ return mPrimaryId;
+ }
+
+ /**
+ * Secondary program identifier is not required for tuning, but may make it
+ * faster or more reliable.
+ *
+ * @returns secondary identifier list, must not be modified.
+ */
+ public @NonNull Identifier[] getSecondaryIds() {
+ return mSecondaryIds;
+ }
+
+ /**
+ * Looks up an identifier of a given type (either primary or secondary).
+ *
+ * If there are multiple identifiers if a given type, then first in order (where primary id is
+ * before any secondary) is selected.
+ *
+ * @param type type of identifier.
+ * @return identifier value, if found.
+ * @throws IllegalArgumentException, if not found.
+ */
+ public long getFirstId(@IdentifierType int type) {
+ if (mPrimaryId.getType() == type) return mPrimaryId.getValue();
+ for (Identifier id : mSecondaryIds) {
+ if (id.getType() == type) return id.getValue();
+ }
+ throw new IllegalArgumentException("Identifier " + type + " not found");
+ }
+
+ /**
+ * Looks up all identifier of a given type (either primary or secondary).
+ *
+ * Some identifiers may be provided multiple times, for example
+ * IDENTIFIER_TYPE_AMFM_FREQUENCY for FM Alternate Frequencies.
+ *
+ * @param type type of identifier.
+ * @return a list of identifiers, generated on each call. May be modified.
+ */
+ public @NonNull Identifier[] getAllIds(@IdentifierType int type) {
+ List<Identifier> out = new ArrayList<>();
+
+ if (mPrimaryId.getType() == type) out.add(mPrimaryId);
+ for (Identifier id : mSecondaryIds) {
+ if (id.getType() == type) out.add(id);
+ }
+
+ return out.toArray(new Identifier[out.size()]);
+ }
+
+ /**
+ * Vendor identifiers are passed as-is to the HAL implementation,
+ * preserving elements order.
+ *
+ * @return a array of vendor identifiers, must not be modified.
+ */
+ public @NonNull long[] getVendorIds() {
+ return mVendorIds;
+ }
+
+ /**
+ * Builds new ProgramSelector for AM/FM frequency.
+ *
+ * @param band the band.
+ * @param frequencyKhz the frequency in kHz.
+ * @return new ProgramSelector object representing given frequency.
+ * @throws IllegalArgumentException if provided frequency is out of bounds.
+ */
+ public static @NonNull ProgramSelector createAmFmSelector(
+ @RadioManager.Band int band, int frequencyKhz) {
+ return createAmFmSelector(band, frequencyKhz, 0);
+ }
+
+ /**
+ * Checks, if a given AM/FM frequency is roughly valid and in correct unit.
+ *
+ * It does not check the range precisely. In particular, it may be way off for certain regions.
+ * The main purpose is to avoid passing inproper units, ie. MHz instead of kHz.
+ *
+ * @param isAm true, if AM, false if FM.
+ * @param frequencyKhz the frequency in kHz.
+ * @return true, if the frequency is rougly valid.
+ */
+ private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) {
+ if (isAm) {
+ return frequencyKhz > 150 && frequencyKhz < 30000;
+ } else {
+ return frequencyKhz > 60000 && frequencyKhz < 110000;
+ }
+ }
+
+ /**
+ * Builds new ProgramSelector for AM/FM frequency.
+ *
+ * This method variant supports HD Radio subchannels, but it's undesirable to
+ * select them manually. Instead, the value should be retrieved from program list.
+ *
+ * @param band the band.
+ * @param frequencyKhz the frequency in kHz.
+ * @param subChannel 1-based HD Radio subchannel.
+ * @return new ProgramSelector object representing given frequency.
+ * @throws IllegalArgumentException if provided frequency is out of bounds,
+ * or tried setting a subchannel for analog AM/FM.
+ */
+ public static @NonNull ProgramSelector createAmFmSelector(
+ @RadioManager.Band int band, int frequencyKhz, int subChannel) {
+ boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD);
+ boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD);
+ if (!isAm && !isDigital && band != RadioManager.BAND_FM) {
+ throw new IllegalArgumentException("Unknown band: " + band);
+ }
+ if (subChannel < 0 || subChannel > 8) {
+ throw new IllegalArgumentException("Invalid subchannel: " + subChannel);
+ }
+ if (subChannel > 0 && !isDigital) {
+ throw new IllegalArgumentException("Subchannels are not supported for non-HD radio");
+ }
+ if (!isValidAmFmFrequency(isAm, frequencyKhz)) {
+ throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency");
+ }
+
+ // We can't use AM_HD or FM_HD, because we don't know HD station ID.
+ @ProgramType int programType = isAm ? PROGRAM_TYPE_AM : PROGRAM_TYPE_FM;
+ Identifier primary = new Identifier(IDENTIFIER_TYPE_AMFM_FREQUENCY, frequencyKhz);
+
+ Identifier[] secondary = null;
+ if (subChannel > 0) {
+ /* Stating sub channel for non-HD AM/FM does not give any guarantees,
+ * but we can't do much more without HD station ID.
+ *
+ * The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based.
+ */
+ secondary = new Identifier[]{
+ new Identifier(IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)};
+ }
+
+ return new ProgramSelector(programType, primary, secondary, null);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
+ .append(", primary=").append(mPrimaryId);
+ if (mSecondaryIds.length > 0) sb.append(", secondary=").append(mSecondaryIds);
+ if (mVendorIds.length > 0) sb.append(", vendor=").append(mVendorIds);
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ // secondaryIds and vendorIds are ignored for equality/hashing
+ return Objects.hash(mProgramType, mPrimaryId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof ProgramSelector)) return false;
+ ProgramSelector other = (ProgramSelector) obj;
+ // secondaryIds and vendorIds are ignored for equality/hashing
+ return other.getProgramType() == mProgramType && mPrimaryId.equals(other.getPrimaryId());
+ }
+
+ private ProgramSelector(Parcel in) {
+ mProgramType = in.readInt();
+ mPrimaryId = in.readTypedObject(Identifier.CREATOR);
+ mSecondaryIds = in.createTypedArray(Identifier.CREATOR);
+ if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) {
+ throw new IllegalArgumentException("secondaryIds list must not contain nulls");
+ }
+ mVendorIds = in.createLongArray();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mProgramType);
+ dest.writeTypedObject(mPrimaryId, 0);
+ dest.writeTypedArray(mSecondaryIds, 0);
+ dest.writeLongArray(mVendorIds);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<ProgramSelector> CREATOR =
+ new Parcelable.Creator<ProgramSelector>() {
+ public ProgramSelector createFromParcel(Parcel in) {
+ return new ProgramSelector(in);
+ }
+
+ public ProgramSelector[] newArray(int size) {
+ return new ProgramSelector[size];
+ }
+ };
+
+ /**
+ * A single program identifier component, eg. frequency or channel ID.
+ *
+ * The long value field holds the value in format described in comments for
+ * IdentifierType constants.
+ */
+ public static final class Identifier implements Parcelable {
+ private final @IdentifierType int mType;
+ private final long mValue;
+
+ public Identifier(@IdentifierType int type, long value) {
+ mType = type;
+ mValue = value;
+ }
+
+ /**
+ * Type of an identifier.
+ *
+ * @return type of an identifier.
+ */
+ public @IdentifierType int getType() {
+ return mType;
+ }
+
+ /**
+ * Value of an identifier.
+ *
+ * Its meaning depends on identifier type, ie. for IDENTIFIER_TYPE_AMFM_FREQUENCY type,
+ * the value is a frequency in kHz.
+ *
+ * The range of a value depends on its type; it does not always require the whole long
+ * range. Casting to necessary type (ie. int) without range checking is correct in front-end
+ * code - any range violations are either errors in the framework or in the
+ * HAL implementation. For example, IDENTIFIER_TYPE_AMFM_FREQUENCY always fits in int,
+ * as Integer.MAX_VALUE would mean 2.1THz.
+ *
+ * @return value of an identifier.
+ */
+ public long getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return "Identifier(" + mType + ", " + mValue + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mValue);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof Identifier)) return false;
+ Identifier other = (Identifier) obj;
+ return other.getType() == mType && other.getValue() == mValue;
+ }
+
+ private Identifier(Parcel in) {
+ mType = in.readInt();
+ mValue = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeLong(mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Identifier> CREATOR =
+ new Parcelable.Creator<Identifier>() {
+ public Identifier createFromParcel(Parcel in) {
+ return new Identifier(in);
+ }
+
+ public Identifier[] newArray(int size) {
+ return new Identifier[size];
+ }
+ };
+ }
+}
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index d25e752..8ef5705 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -16,6 +16,7 @@
package android.hardware.radio;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -29,6 +30,8 @@
import android.text.TextUtils;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;
@@ -70,7 +73,7 @@
/** Radio module class supporting Digital terrestrial radio */
public static final int CLASS_DT = 2;
- // keep in sync with radio_band_t in /system/core/incluse/system/radio.h
+ public static final int BAND_INVALID = -1;
/** AM radio band (LW/MW/SW).
* @see BandDescriptor */
public static final int BAND_AM = 0;
@@ -83,6 +86,15 @@
/** AM HD radio or DRM band.
* @see BandDescriptor */
public static final int BAND_AM_HD = 3;
+ @IntDef(prefix = { "BAND_" }, value = {
+ BAND_INVALID,
+ BAND_AM,
+ BAND_FM,
+ BAND_AM_HD,
+ BAND_FM_HD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Band {}
// keep in sync with radio_region_t in /system/core/incluse/system/radio.h
/** Africa, Europe.
@@ -1258,8 +1270,7 @@
private final static int FLAG_LIVE = 1 << 0;
private final static int FLAG_MUTED = 1 << 1;
- private final int mChannel;
- private final int mSubChannel;
+ @NonNull private final ProgramSelector mSelector;
private final boolean mTuned;
private final boolean mStereo;
private final boolean mDigital;
@@ -1268,11 +1279,10 @@
private final RadioMetadata mMetadata;
private final String mVendorExension;
- ProgramInfo(int channel, int subChannel, boolean tuned, boolean stereo,
+ ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
boolean digital, int signalStrength, RadioMetadata metadata, int flags,
String vendorExension) {
- mChannel = channel;
- mSubChannel = subChannel;
+ mSelector = selector;
mTuned = tuned;
mStereo = stereo;
mDigital = digital;
@@ -1282,19 +1292,45 @@
mVendorExension = vendorExension;
}
+ /**
+ * Program selector, necessary for tuning to a program.
+ *
+ * @return the program selector.
+ */
+ public @NonNull ProgramSelector getSelector() {
+ return mSelector;
+ }
+
/** Main channel expressed in units according to band type.
* Currently all defined band types express channels as frequency in kHz
* @return the program channel
+ * @deprecated Use {@link getSelector()} instead.
*/
+ @Deprecated
public int getChannel() {
- return mChannel;
+ try {
+ return (int) mSelector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "Not an AM/FM program");
+ return 0;
+ }
}
+
/** Sub channel ID. E.g 1 for HD radio HD1
* @return the program sub channel
+ * @deprecated Use {@link getSelector()} instead.
*/
+ @Deprecated
public int getSubChannel() {
- return mSubChannel;
+ try {
+ return (int) mSelector.getFirstId(
+ ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL) + 1;
+ } catch (IllegalArgumentException ex) {
+ // this is a normal behavior for analog AM/FM selector
+ return 0;
+ }
}
+
/** {@code true} if the tuner is currently tuned on a valid station
* @return {@code true} if currently tuned, {@code false} otherwise.
*/
@@ -1362,8 +1398,7 @@
}
private ProgramInfo(Parcel in) {
- mChannel = in.readInt();
- mSubChannel = in.readInt();
+ mSelector = in.readParcelable(null);
mTuned = in.readByte() == 1;
mStereo = in.readByte() == 1;
mDigital = in.readByte() == 1;
@@ -1390,8 +1425,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mChannel);
- dest.writeInt(mSubChannel);
+ dest.writeParcelable(mSelector, 0);
dest.writeByte((byte)(mTuned ? 1 : 0));
dest.writeByte((byte)(mStereo ? 1 : 0));
dest.writeByte((byte)(mDigital ? 1 : 0));
@@ -1413,7 +1447,7 @@
@Override
public String toString() {
- return "ProgramInfo [mChannel=" + mChannel + ", mSubChannel=" + mSubChannel
+ return "ProgramInfo [mSelector=" + mSelector
+ ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
+ ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength
+ ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
@@ -1424,8 +1458,7 @@
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + mChannel;
- result = prime * result + mSubChannel;
+ result = prime * result + mSelector.hashCode();
result = prime * result + (mTuned ? 1 : 0);
result = prime * result + (mStereo ? 1 : 0);
result = prime * result + (mDigital ? 1 : 0);
@@ -1443,10 +1476,7 @@
if (!(obj instanceof ProgramInfo))
return false;
ProgramInfo other = (ProgramInfo) obj;
- if (mChannel != other.getChannel())
- return false;
- if (mSubChannel != other.getSubChannel())
- return false;
+ if (!mSelector.equals(other.getSelector())) return false;
if (mTuned != other.isTuned())
return false;
if (mStereo != other.isStereo())
@@ -1541,7 +1571,7 @@
Log.e(TAG, "Failed to open tuner");
return null;
}
- return new TunerAdapter(tuner);
+ return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID);
}
@NonNull private final Context mContext;
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index 61e62ea..f659273 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -170,10 +170,22 @@
* <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails, </li>
* </ul>
+ * @deprecated Use {@link tune(ProgramSelector)} instead.
*/
+ @Deprecated
public abstract int tune(int channel, int subChannel);
/**
+ * Tune to a program.
+ *
+ * The operation is asynchronous and {@link Callback} onProgramInfoChanged() will be called
+ * when tune completes or onError() when cancelled or on timeout.
+ *
+ * @thows IllegalArgumentException if the provided selector is invalid
+ */
+ public abstract void tune(@NonNull ProgramSelector selector);
+
+ /**
* Cancel a pending scan or tune operation.
* If an operation is pending, {@link Callback} onError() will be called with
* {@link #ERROR_CANCELLED}.
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index dbaca62..503bb55 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -32,11 +32,14 @@
@NonNull private final ITuner mTuner;
private boolean mIsClosed = false;
- TunerAdapter(ITuner tuner) {
+ private @RadioManager.Band int mBand;
+
+ TunerAdapter(ITuner tuner, @RadioManager.Band int band) {
if (tuner == null) {
throw new NullPointerException();
}
mTuner = tuner;
+ mBand = band;
}
@Override
@@ -59,6 +62,7 @@
public int setConfiguration(RadioManager.BandConfig config) {
try {
mTuner.setConfiguration(config);
+ mBand = config.getType();
return RadioManager.STATUS_OK;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Can't set configuration", e);
@@ -138,7 +142,7 @@
@Override
public int tune(int channel, int subChannel) {
try {
- mTuner.tune(channel, subChannel);
+ mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel));
} catch (IllegalStateException e) {
Log.e(TAG, "Can't tune", e);
return RadioManager.STATUS_INVALID_OPERATION;
@@ -153,6 +157,15 @@
}
@Override
+ public void tune(@NonNull ProgramSelector selector) {
+ try {
+ mTuner.tune(selector);
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
public int cancel() {
try {
mTuner.cancel();
diff --git a/services/core/java/com/android/server/radio/Tuner.java b/services/core/java/com/android/server/radio/Tuner.java
index 81128c2..209febc 100644
--- a/services/core/java/com/android/server/radio/Tuner.java
+++ b/services/core/java/com/android/server/radio/Tuner.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.IBinder;
import android.os.RemoteException;
@@ -44,12 +45,13 @@
private int mRegion; // TODO(b/62710330): find better solution to handle regions
private final boolean mWithAudio;
- Tuner(@NonNull ITunerCallback clientCallback, int halRev, int region, boolean withAudio) {
+ Tuner(@NonNull ITunerCallback clientCallback, int halRev,
+ int region, boolean withAudio, int band) {
mClientCallback = clientCallback;
mTunerCallback = new TunerCallback(this, clientCallback, halRev);
mRegion = region;
mWithAudio = withAudio;
- mNativeContext = nativeInit(halRev, withAudio);
+ mNativeContext = nativeInit(halRev, withAudio, band);
mDeathRecipient = this::close;
try {
mClientCallback.asBinder().linkToDeath(mDeathRecipient, 0);
@@ -64,7 +66,7 @@
super.finalize();
}
- private native long nativeInit(int halRev, boolean withAudio);
+ private native long nativeInit(int halRev, boolean withAudio, int band);
private native void nativeFinalize(long nativeContext);
private native void nativeClose(long nativeContext);
@@ -74,7 +76,7 @@
private native void nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel);
private native void nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel);
- private native void nativeTune(long nativeContext, int channel, int subChannel);
+ private native void nativeTune(long nativeContext, @NonNull ProgramSelector selector);
private native void nativeCancel(long nativeContext);
private native RadioManager.ProgramInfo nativeGetProgramInformation(long nativeContext);
@@ -173,10 +175,14 @@
}
@Override
- public void tune(int channel, int subChannel) {
+ public void tune(ProgramSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("The argument must not be a null pointer");
+ }
+ Slog.i(TAG, "Tuning to " + selector);
synchronized (mLock) {
checkNotClosedLocked();
- nativeTune(mNativeContext, channel, subChannel);
+ nativeTune(mNativeContext, selector);
}
}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index c8c629f..a6065025 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -108,4 +108,6 @@
android.frameworks.schedulerservice@1.0 \
android.frameworks.sensorservice@1.0 \
-LOCAL_STATIC_LIBRARIES += libscrypt_static
+LOCAL_STATIC_LIBRARIES += \
+ android.hardware.broadcastradio@1.1-utils-lib \
+ libscrypt_static \
diff --git a/services/core/jni/com_android_server_radio_RadioService.cpp b/services/core/jni/com_android_server_radio_RadioService.cpp
index fa64901..459c0d0 100644
--- a/services/core/jni/com_android_server_radio_RadioService.cpp
+++ b/services/core/jni/com_android_server_radio_RadioService.cpp
@@ -210,7 +210,7 @@
BandConfig bandConfigHal = convert::BandConfigToHal(env, bandConfig, region);
auto tuner = make_javaref(env, env->NewObject(gjni.Tuner.clazz, gjni.Tuner.cstor,
- callback, halRev, region, withAudio));
+ callback, halRev, region, withAudio, bandConfigHal.type));
if (tuner == nullptr) {
ALOGE("Unable to create new tuner object.");
return nullptr;
@@ -258,7 +258,7 @@
auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
gjni.Tuner.clazz = MakeGlobalRefOrDie(env, tunerClass);
gjni.Tuner.cstor = GetMethodIDOrDie(env, tunerClass, "<init>",
- "(Landroid/hardware/radio/ITunerCallback;IIZ)V");
+ "(Landroid/hardware/radio/ITunerCallback;IIZI)V");
auto arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
gjni.ArrayList.clazz = MakeGlobalRefOrDie(env, arrayListClass);
diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp
index 4b6f30b..13bfb57 100644
--- a/services/core/jni/com_android_server_radio_Tuner.cpp
+++ b/services/core/jni/com_android_server_radio_Tuner.cpp
@@ -22,12 +22,13 @@
#include "com_android_server_radio_convert.h"
#include "com_android_server_radio_TunerCallback.h"
+#include <JNIHelp.h>
+#include <Utils.h>
#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
#include <binder/IPCThreadState.h>
#include <core_jni_helpers.h>
#include <media/AudioSystem.h>
#include <utils/Log.h>
-#include <JNIHelp.h>
namespace android {
namespace server {
@@ -41,6 +42,7 @@
namespace V1_0 = hardware::broadcastradio::V1_0;
namespace V1_1 = hardware::broadcastradio::V1_1;
+using V1_0::Band;
using V1_0::BandConfig;
using V1_0::MetaData;
using V1_0::Result;
@@ -79,6 +81,7 @@
bool mIsClosed = false;
HalRevision mHalRev;
bool mWithAudio;
+ Band mBand;
sp<V1_0::ITuner> mHalTuner;
sp<V1_1::ITuner> mHalTuner11;
sp<HalDeathRecipient> mHalDeathRecipient;
@@ -100,13 +103,14 @@
return getNativeContext(env->GetLongField(jTuner.get(), gjni.Tuner.nativeContext));
}
-static jlong nativeInit(JNIEnv *env, jobject obj, jint halRev, bool withAudio) {
+static jlong nativeInit(JNIEnv *env, jobject obj, jint halRev, bool withAudio, jint band) {
ALOGV("nativeInit()");
AutoMutex _l(gContextMutex);
auto ctx = new TunerContext();
ctx->mHalRev = static_cast<HalRevision>(halRev);
ctx->mWithAudio = withAudio;
+ ctx->mBand = static_cast<Band>(band);
static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer");
return reinterpret_cast<jlong>(ctx);
@@ -170,13 +174,17 @@
notifyAudioService(ctx, true);
}
-sp<V1_0::ITuner> getHalTuner(jlong nativeContext) {
- AutoMutex _l(gContextMutex);
- auto tuner = getNativeContext(nativeContext).mHalTuner;
+static sp<V1_0::ITuner> getHalTuner(const TunerContext& ctx) {
+ auto tuner = ctx.mHalTuner;
LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner is not open");
return tuner;
}
+sp<V1_0::ITuner> getHalTuner(jlong nativeContext) {
+ AutoMutex _l(gContextMutex);
+ return getHalTuner(getNativeContext(nativeContext));
+}
+
sp<V1_1::ITuner> getHalTuner11(jlong nativeContext) {
AutoMutex _l(gContextMutex);
return getNativeContext(nativeContext).mHalTuner11;
@@ -215,13 +223,17 @@
static void nativeSetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, jobject config) {
ALOGV("nativeSetConfiguration()");
- auto halTuner = getHalTuner(nativeContext);
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(nativeContext);
+ auto halTuner = getHalTuner(ctx);
if (halTuner == nullptr) return;
Region region_unused;
BandConfig bandConfigHal = convert::BandConfigToHal(env, config, region_unused);
- convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal));
+ if (convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal))) return;
+
+ ctx.mBand = bandConfigHal.type;
}
static jobject nativeGetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext,
@@ -263,13 +275,26 @@
convert::ThrowIfFailed(env, halTuner->scan(dir, skipSubChannel));
}
-static void nativeTune(JNIEnv *env, jobject obj, jlong nativeContext,
- jint channel, jint subChannel) {
- ALOGV("nativeTune(%d, %d)", channel, subChannel);
- auto halTuner = getHalTuner(nativeContext);
- if (halTuner == nullptr) return;
+static void nativeTune(JNIEnv *env, jobject obj, jlong nativeContext, jobject jSelector) {
+ ALOGV("nativeTune()");
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(nativeContext);
+ auto halTuner10 = getHalTuner(ctx);
+ auto halTuner11 = ctx.mHalTuner11;
+ if (halTuner10 == nullptr) return;
- convert::ThrowIfFailed(env, halTuner->tune(channel, subChannel));
+ auto selector = convert::ProgramSelectorToHal(env, jSelector);
+ if (halTuner11 != nullptr) {
+ convert::ThrowIfFailed(env, halTuner11->tune_1_1(selector));
+ } else {
+ uint32_t channel, subChannel;
+ if (!V1_1::utils::getLegacyChannel(selector, &channel, &subChannel)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Can't tune to non-AM/FM channel with HAL<1.1");
+ return;
+ }
+ convert::ThrowIfFailed(env, halTuner10->tune(channel, subChannel));
+ }
}
static void nativeCancel(JNIEnv *env, jobject obj, jlong nativeContext) {
@@ -282,8 +307,10 @@
static jobject nativeGetProgramInformation(JNIEnv *env, jobject obj, jlong nativeContext) {
ALOGV("nativeGetProgramInformation()");
- auto halTuner10 = getHalTuner(nativeContext);
- auto halTuner11 = getHalTuner11(nativeContext);
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(nativeContext);
+ auto halTuner10 = getHalTuner(ctx);
+ auto halTuner11 = ctx.mHalTuner11;
if (halTuner10 == nullptr) return nullptr;
JavaRef<jobject> jInfo;
@@ -301,7 +328,7 @@
const V1_0::ProgramInfo& info) {
halResult = result;
if (result != Result::OK) return;
- jInfo = convert::ProgramInfoFromHal(env, info);
+ jInfo = convert::ProgramInfoFromHal(env, info, ctx.mBand);
});
}
@@ -402,7 +429,7 @@
}
static const JNINativeMethod gTunerMethods[] = {
- { "nativeInit", "(IZ)J", (void*)nativeInit },
+ { "nativeInit", "(IZI)J", (void*)nativeInit },
{ "nativeFinalize", "(J)V", (void*)nativeFinalize },
{ "nativeClose", "(J)V", (void*)nativeClose },
{ "nativeSetConfiguration", "(JLandroid/hardware/radio/RadioManager$BandConfig;)V",
@@ -411,7 +438,7 @@
(void*)nativeGetConfiguration },
{ "nativeStep", "(JZZ)V", (void*)nativeStep },
{ "nativeScan", "(JZZ)V", (void*)nativeScan },
- { "nativeTune", "(JII)V", (void*)nativeTune },
+ { "nativeTune", "(JLandroid/hardware/radio/ProgramSelector;)V", (void*)nativeTune },
{ "nativeCancel", "(J)V", (void*)nativeCancel },
{ "nativeGetProgramInformation", "(J)Landroid/hardware/radio/RadioManager$ProgramInfo;",
(void*)nativeGetProgramInformation },
diff --git a/services/core/jni/com_android_server_radio_convert.cpp b/services/core/jni/com_android_server_radio_convert.cpp
index 55fb10f..af000bd 100644
--- a/services/core/jni/com_android_server_radio_convert.cpp
+++ b/services/core/jni/com_android_server_radio_convert.cpp
@@ -19,9 +19,10 @@
#include "com_android_server_radio_convert.h"
+#include <JNIHelp.h>
+#include <Utils.h>
#include <core_jni_helpers.h>
#include <utils/Log.h>
-#include <JNIHelp.h>
namespace android {
namespace server {
@@ -37,7 +38,9 @@
using V1_0::MetadataType;
using V1_0::Result;
using V1_0::Rds;
+using V1_1::ProgramIdentifier;
using V1_1::ProgramListResult;
+using V1_1::ProgramSelector;
static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region);
@@ -90,6 +93,22 @@
struct {
jclass clazz;
jmethodID cstor;
+ jfieldID programType;
+ jfieldID primaryId;
+ jfieldID secondaryIds;
+ jfieldID vendorIds;
+
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ jfieldID type;
+ jfieldID value;
+ } Identifier;
+ } ProgramSelector;
+
+ struct {
+ jclass clazz;
+ jmethodID cstor;
jmethodID putIntFromNative;
jmethodID putStringFromNative;
jmethodID putBitmapFromNative;
@@ -383,25 +402,100 @@
return jMetadata;
}
+static JavaRef<jobject> ProgramIdentifierFromHal(JNIEnv *env, const ProgramIdentifier &id) {
+ ALOGV("ProgramIdentifierFromHal()");
+ return make_javaref(env, env->NewObject(gjni.ProgramSelector.Identifier.clazz,
+ gjni.ProgramSelector.Identifier.cstor, id.type, id.value));
+}
+
+static JavaRef<jobject> ProgramSelectorFromHal(JNIEnv *env, const ProgramSelector &selector) {
+ ALOGV("ProgramSelectorFromHal()");
+ auto jPrimary = ProgramIdentifierFromHal(env, selector.primaryId);
+
+ auto jSecondary = make_javaref(env, env->NewObjectArray(selector.secondaryIds.size(),
+ gjni.ProgramSelector.Identifier.clazz, nullptr));
+ for (size_t i = 0; i < selector.secondaryIds.size(); i++) {
+ auto jId = ProgramIdentifierFromHal(env, selector.secondaryIds[i]);
+ env->SetObjectArrayElement(jSecondary.get(), i, jId.get());
+ }
+
+ auto jVendor = make_javaref(env, env->NewLongArray(selector.vendorIds.size()));
+ auto jVendorElements = env->GetLongArrayElements(jVendor.get(), nullptr);
+ for (size_t i = 0; i < selector.vendorIds.size(); i++) {
+ jVendorElements[i] = selector.vendorIds[i];
+ }
+ env->ReleaseLongArrayElements(jVendor.get(), jVendorElements, 0);
+
+ return make_javaref(env, env->NewObject(gjni.ProgramSelector.clazz, gjni.ProgramSelector.cstor,
+ selector.programType, jPrimary.get(), jSecondary.get(), jVendor.get()));
+}
+
+static ProgramIdentifier ProgramIdentifierToHal(JNIEnv *env, jobject jId) {
+ ALOGV("ProgramIdentifierToHal()");
+
+ ProgramIdentifier id = {};
+ id.type = env->GetIntField(jId, gjni.ProgramSelector.Identifier.type);
+ id.value = env->GetLongField(jId, gjni.ProgramSelector.Identifier.value);
+ return id;
+}
+
+ProgramSelector ProgramSelectorToHal(JNIEnv *env, jobject jSelector) {
+ ALOGV("ProgramSelectorToHal()");
+
+ ProgramSelector selector = {};
+
+ selector.programType = env->GetIntField(jSelector, gjni.ProgramSelector.programType);
+
+ auto jPrimary = env->GetObjectField(jSelector, gjni.ProgramSelector.primaryId);
+ auto jSecondary = reinterpret_cast<jobjectArray>(
+ env->GetObjectField(jSelector, gjni.ProgramSelector.secondaryIds));
+ auto jVendor = reinterpret_cast<jlongArray>(
+ env->GetObjectField(jSelector, gjni.ProgramSelector.vendorIds));
+
+ if (jPrimary == nullptr || jSecondary == nullptr || jVendor == nullptr) {
+ ALOGE("ProgramSelector object is incomplete");
+ return {};
+ }
+
+ selector.primaryId = ProgramIdentifierToHal(env, jPrimary);
+ auto count = env->GetArrayLength(jSecondary);
+ selector.secondaryIds.resize(count);
+ for (jsize i = 0; i < count; i++) {
+ auto jId = env->GetObjectArrayElement(jSecondary, i);
+ selector.secondaryIds[i] = ProgramIdentifierToHal(env, jId);
+ }
+
+ count = env->GetArrayLength(jVendor);
+ selector.vendorIds.resize(count);
+ auto jVendorElements = env->GetLongArrayElements(jVendor, nullptr);
+ for (jint i = 0; i < count; i++) {
+ selector.vendorIds[i] = jVendorElements[i];
+ }
+ env->ReleaseLongArrayElements(jVendor, jVendorElements, 0);
+
+ return selector;
+}
+
static JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info10,
- const V1_1::ProgramInfo *info11) {
+ const V1_1::ProgramInfo *info11, const ProgramSelector &selector) {
ALOGV("ProgramInfoFromHal()");
auto jMetadata = MetadataFromHal(env, info10.metadata);
auto jVendorExtension = info11 ? make_javastr(env, info11->vendorExension) : nullptr;
+ auto jSelector = ProgramSelectorFromHal(env, selector);
return make_javaref(env, env->NewObject(gjni.ProgramInfo.clazz, gjni.ProgramInfo.cstor,
- info10.channel, info10.subChannel, info10.tuned, info10.stereo, info10.digital,
- info10.signalStrength, jMetadata.get(), info11 ? info11->flags : 0,
- jVendorExtension.get()));
+ jSelector.get(), info10.tuned, info10.stereo, info10.digital, info10.signalStrength,
+ jMetadata.get(), info11 ? info11->flags : 0, jVendorExtension.get()));
}
-JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info) {
- return ProgramInfoFromHal(env, info, nullptr);
+JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info, V1_0::Band band) {
+ auto selector = V1_1::utils::make_selector(band, info.channel, info.subChannel);
+ return ProgramInfoFromHal(env, info, nullptr, selector);
}
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_1::ProgramInfo &info) {
- return ProgramInfoFromHal(env, info.base, &info);
+ return ProgramInfoFromHal(env, info.base, &info, info.selector);
}
} // namespace convert
@@ -465,7 +559,31 @@
auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo");
gjni.ProgramInfo.clazz = MakeGlobalRefOrDie(env, programInfoClass);
gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>",
- "(IIZZZILandroid/hardware/radio/RadioMetadata;ILjava/lang/String;)V");
+ "(Landroid/hardware/radio/ProgramSelector;ZZZILandroid/hardware/radio/RadioMetadata;I"
+ "Ljava/lang/String;)V");
+
+ auto programSelectorClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector");
+ gjni.ProgramSelector.clazz = MakeGlobalRefOrDie(env, programSelectorClass);
+ gjni.ProgramSelector.cstor = GetMethodIDOrDie(env, programSelectorClass, "<init>",
+ "(ILandroid/hardware/radio/ProgramSelector$Identifier;"
+ "[Landroid/hardware/radio/ProgramSelector$Identifier;[J)V");
+ gjni.ProgramSelector.programType = GetFieldIDOrDie(env, programSelectorClass,
+ "mProgramType", "I");
+ gjni.ProgramSelector.primaryId = GetFieldIDOrDie(env, programSelectorClass,
+ "mPrimaryId", "Landroid/hardware/radio/ProgramSelector$Identifier;");
+ gjni.ProgramSelector.secondaryIds = GetFieldIDOrDie(env, programSelectorClass,
+ "mSecondaryIds", "[Landroid/hardware/radio/ProgramSelector$Identifier;");
+ gjni.ProgramSelector.vendorIds = GetFieldIDOrDie(env, programSelectorClass,
+ "mVendorIds", "[J");
+
+ auto progSelIdClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector$Identifier");
+ gjni.ProgramSelector.Identifier.clazz = MakeGlobalRefOrDie(env, progSelIdClass);
+ gjni.ProgramSelector.Identifier.cstor = GetMethodIDOrDie(env, progSelIdClass,
+ "<init>", "(IJ)V");
+ gjni.ProgramSelector.Identifier.type = GetFieldIDOrDie(env, progSelIdClass,
+ "mType", "I");
+ gjni.ProgramSelector.Identifier.value = GetFieldIDOrDie(env, progSelIdClass,
+ "mValue", "J");
auto radioMetadataClass = FindClassOrDie(env, "android/hardware/radio/RadioMetadata");
gjni.RadioMetadata.clazz = MakeGlobalRefOrDie(env, radioMetadataClass);
diff --git a/services/core/jni/com_android_server_radio_convert.h b/services/core/jni/com_android_server_radio_convert.h
index 3ba6fed..8b91ced 100644
--- a/services/core/jni/com_android_server_radio_convert.h
+++ b/services/core/jni/com_android_server_radio_convert.h
@@ -45,9 +45,10 @@
V1_0::Direction DirectionToHal(bool directionDown);
JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hardware::hidl_vec<V1_0::MetaData> &metadata);
-JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info);
+JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info, V1_0::Band band);
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_1::ProgramInfo &info);
+V1_1::ProgramSelector ProgramSelectorToHal(JNIEnv *env, jobject jSelector);
void ThrowParcelableRuntimeException(JNIEnv *env, const std::string& msg);
diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
index e354a34..0c6d0e2 100644
--- a/tests/radio/src/android/hardware/radio/tests/RadioTest.java
+++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
import android.support.test.InstrumentationRegistry;
@@ -64,6 +65,7 @@
private final int kConfigCallbackTimeoutMs = 10000;
private final int kCancelTimeoutMs = 1000;
private final int kTuneCallbackTimeoutMs = 30000;
+ private final int kFullScanTimeoutMs = 60000;
private RadioManager mRadioManager;
private RadioTuner mRadioTuner;
@@ -144,11 +146,9 @@
assertNotNull(mRadioTuner);
verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
resetCallback();
- }
- private void checkAntenna() {
- boolean isConnected = mRadioTuner.isAntennaConnected();
- assertTrue(isConnected);
+ boolean isAntennaConnected = mRadioTuner.isAntennaConnected();
+ assertTrue(isAntennaConnected);
}
@Test
@@ -256,7 +256,6 @@
@Test
public void testStep() {
openTuner();
- checkAntenna();
int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
assertEquals(RadioManager.STATUS_OK, ret);
@@ -272,7 +271,6 @@
@Test
public void testTuneAndGetPI() {
openTuner();
- checkAntenna();
int channel = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing();
@@ -304,7 +302,6 @@
@Test
public void testLateCancel() {
openTuner();
- checkAntenna();
int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, false);
assertEquals(RadioManager.STATUS_OK, ret);
@@ -317,7 +314,6 @@
@Test
public void testScanAndCancel() {
openTuner();
- checkAntenna();
/* There is a possible race condition between scan and cancel commands - the scan may finish
* before cancel command is issued. Thus we accept both outcomes in this test.
@@ -335,7 +331,6 @@
@Test
public void testStartBackgroundScan() {
openTuner();
- checkAntenna();
boolean ret = mRadioTuner.startBackgroundScan();
boolean isSupported = mModule.isBackgroundScanningSupported();
@@ -345,7 +340,6 @@
@Test
public void testGetProgramList() {
openTuner();
- checkAntenna();
try {
List<RadioManager.ProgramInfo> list = mRadioTuner.getProgramList(null);
@@ -357,6 +351,39 @@
}
@Test
+ public void testTuneFromProgramList() {
+ openTuner();
+
+ List<RadioManager.ProgramInfo> list;
+
+ try {
+ list = mRadioTuner.getProgramList(null);
+ assertNotNull(list);
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Background list is not ready, trying to fix it");
+
+ boolean success = mRadioTuner.startBackgroundScan();
+ assertTrue(success);
+ verify(mCallback, timeout(kFullScanTimeoutMs)).onBackgroundScanComplete();
+
+ list = mRadioTuner.getProgramList(null);
+ assertNotNull(list);
+ }
+
+ if (list.isEmpty()) {
+ Log.i(TAG, "Program list is empty, can't test tune");
+ return;
+ }
+
+ ProgramSelector sel = list.get(0).getSelector();
+ mRadioTuner.tune(sel);
+ ArgumentCaptor<RadioManager.ProgramInfo> infoc =
+ ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
+ verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(infoc.capture());
+ assertEquals(sel, infoc.getValue().getSelector());
+ }
+
+ @Test
public void testForcedAnalog() {
openTuner();