summaryrefslogtreecommitdiff
path: root/android/hardware/radio
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
commit10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch)
tree8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/hardware/radio
parent677516fb6b6f207d373984757d3d9450474b6b00 (diff)
downloadandroid-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \ --bid 4335822 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4335822.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/hardware/radio')
-rw-r--r--android/hardware/radio/ProgramSelector.java502
-rw-r--r--android/hardware/radio/RadioManager.java1673
-rw-r--r--android/hardware/radio/RadioMetadata.java578
-rw-r--r--android/hardware/radio/RadioTuner.java435
-rw-r--r--android/hardware/radio/TunerAdapter.java271
-rw-r--r--android/hardware/radio/TunerCallbackAdapter.java97
6 files changed, 3556 insertions, 0 deletions
diff --git a/android/hardware/radio/ProgramSelector.java b/android/hardware/radio/ProgramSelector.java
new file mode 100644
index 00000000..2211cee9
--- /dev/null
+++ b/android/hardware/radio/ProgramSelector.java
@@ -0,0 +1,502 @@
+/**
+ * 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.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.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_VENDOR_START = 1000;
+ public static final int PROGRAM_TYPE_VENDOR_END = 1999;
+ @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,
+ })
+ @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END)
+ @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;
+ /** 1: AM, 2:FM */
+ public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
+ /** 32bit */
+ public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
+ /** 0-999 range */
+ public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
+ /**
+ * 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 corresponding VENDOR
+ * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
+ * not be used in any program type other than 1015).
+ */
+ public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START;
+ public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END;
+ @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_DRMO_MODULATION,
+ IDENTIFIER_TYPE_SXM_SERVICE_ID,
+ IDENTIFIER_TYPE_SXM_CHANNEL,
+ })
+ @IntRange(from = IDENTIFIER_TYPE_VENDOR_PRIMARY_START, to = IDENTIFIER_TYPE_VENDOR_PRIMARY_END)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IdentifierType {}
+
+ private final @ProgramType int mProgramType;
+ private final @NonNull Identifier mPrimaryId;
+ private final @NonNull Identifier[] mSecondaryIds;
+ private final @NonNull long[] mVendorIds;
+
+ /**
+ * Constructor for ProgramSelector.
+ *
+ * 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.
+ *
+ * @return program type.
+ */
+ public @ProgramType int getProgramType() {
+ return mProgramType;
+ }
+
+ /**
+ * Primary program identifier uniquely identifies a station and is used to
+ * determine equality between two ProgramSelectors.
+ *
+ * @return primary identifier.
+ */
+ public @NonNull Identifier getPrimaryId() {
+ return mPrimaryId;
+ }
+
+ /**
+ * Secondary program identifier is not required for tuning, but may make it
+ * faster or more reliable.
+ *
+ * @return secondary identifier list, must not be modified.
+ */
+ public @NonNull Identifier[] getSecondaryIds() {
+ return mSecondaryIds;
+ }
+
+ /**
+ * Looks up an identifier of a given type (either primary or secondary).
+ *
+ * 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/android/hardware/radio/RadioManager.java b/android/hardware/radio/RadioManager.java
new file mode 100644
index 00000000..4f4361f6
--- /dev/null
+++ b/android/hardware/radio/RadioManager.java
@@ -0,0 +1,1673 @@
+/**
+ * Copyright (C) 2015 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.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * The RadioManager class allows to control a broadcast radio tuner present on the device.
+ * It provides data structures and methods to query for available radio modules, list their
+ * properties and open an interface to control tuning operations and receive callbacks when
+ * asynchronous operations complete or events occur.
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.RADIO_SERVICE)
+public class RadioManager {
+ private static final String TAG = "BroadcastRadio.manager";
+
+ /** Method return status: successful operation */
+ public static final int STATUS_OK = 0;
+ /** Method return status: unspecified error */
+ public static final int STATUS_ERROR = Integer.MIN_VALUE;
+ /** Method return status: permission denied */
+ public static final int STATUS_PERMISSION_DENIED = -1;
+ /** Method return status: initialization failure */
+ public static final int STATUS_NO_INIT = -19;
+ /** Method return status: invalid argument provided */
+ public static final int STATUS_BAD_VALUE = -22;
+ /** Method return status: cannot reach service */
+ public static final int STATUS_DEAD_OBJECT = -32;
+ /** Method return status: invalid or out of sequence operation */
+ public static final int STATUS_INVALID_OPERATION = -38;
+ /** Method return status: time out before operation completion */
+ public static final int STATUS_TIMED_OUT = -110;
+
+
+ // keep in sync with radio_class_t in /system/core/incluse/system/radio.h
+ /** Radio module class supporting FM (including HD radio) and AM */
+ public static final int CLASS_AM_FM = 0;
+ /** Radio module class supporting satellite radio */
+ public static final int CLASS_SAT = 1;
+ /** Radio module class supporting Digital terrestrial radio */
+ public static final int CLASS_DT = 2;
+
+ public static final int BAND_INVALID = -1;
+ /** AM radio band (LW/MW/SW).
+ * @see BandDescriptor */
+ public static final int BAND_AM = 0;
+ /** FM radio band.
+ * @see BandDescriptor */
+ public static final int BAND_FM = 1;
+ /** FM HD radio or DRM band.
+ * @see BandDescriptor */
+ public static final int BAND_FM_HD = 2;
+ /** 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.
+ * @see BandDescriptor */
+ public static final int REGION_ITU_1 = 0;
+ /** Americas.
+ * @see BandDescriptor */
+ public static final int REGION_ITU_2 = 1;
+ /** Russia.
+ * @see BandDescriptor */
+ public static final int REGION_OIRT = 2;
+ /** Japan.
+ * @see BandDescriptor */
+ public static final int REGION_JAPAN = 3;
+ /** Korea.
+ * @see BandDescriptor */
+ public static final int REGION_KOREA = 4;
+
+ private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) {
+ dest.writeInt(map.size());
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+ }
+
+ private static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
+ int size = in.readInt();
+ Map<String, String> map = new HashMap<>();
+ while (size-- > 0) {
+ String key = in.readString();
+ String value = in.readString();
+ map.put(key, value);
+ }
+ return map;
+ }
+
+ /*****************************************************************************
+ * Lists properties, options and radio bands supported by a given broadcast radio module.
+ * Each module has a unique ID used to address it when calling RadioManager APIs.
+ * Module properties are returned by {@link #listModules(List <ModuleProperties>)} method.
+ ****************************************************************************/
+ public static class ModuleProperties implements Parcelable {
+
+ private final int mId;
+ @NonNull private final String mServiceName;
+ private final int mClassId;
+ private final String mImplementor;
+ private final String mProduct;
+ private final String mVersion;
+ private final String mSerial;
+ private final int mNumTuners;
+ private final int mNumAudioSources;
+ private final boolean mIsCaptureSupported;
+ private final BandDescriptor[] mBands;
+ private final boolean mIsBgScanSupported;
+ private final Set<Integer> mSupportedProgramTypes;
+ private final Set<Integer> mSupportedIdentifierTypes;
+ @NonNull private final Map<String, String> mVendorInfo;
+
+ ModuleProperties(int id, String serviceName, int classId, String implementor,
+ String product, String version, String serial, int numTuners, int numAudioSources,
+ boolean isCaptureSupported, BandDescriptor[] bands, boolean isBgScanSupported,
+ @ProgramSelector.ProgramType int[] supportedProgramTypes,
+ @ProgramSelector.IdentifierType int[] supportedIdentifierTypes,
+ Map<String, String> vendorInfo) {
+ mId = id;
+ mServiceName = TextUtils.isEmpty(serviceName) ? "default" : serviceName;
+ mClassId = classId;
+ mImplementor = implementor;
+ mProduct = product;
+ mVersion = version;
+ mSerial = serial;
+ mNumTuners = numTuners;
+ mNumAudioSources = numAudioSources;
+ mIsCaptureSupported = isCaptureSupported;
+ mBands = bands;
+ mIsBgScanSupported = isBgScanSupported;
+ mSupportedProgramTypes = arrayToSet(supportedProgramTypes);
+ mSupportedIdentifierTypes = arrayToSet(supportedIdentifierTypes);
+ mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo;
+ }
+
+ private static Set<Integer> arrayToSet(int[] arr) {
+ return Arrays.stream(arr).boxed().collect(Collectors.toSet());
+ }
+
+ private static int[] setToArray(Set<Integer> set) {
+ return set.stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ /** Unique module identifier provided by the native service.
+ * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}.
+ * @return the radio module unique identifier.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Module service (driver) name as registered with HIDL.
+ * @return the module service name.
+ */
+ public @NonNull String getServiceName() {
+ return mServiceName;
+ }
+
+ /** Module class identifier: {@link #CLASS_AM_FM}, {@link #CLASS_SAT}, {@link #CLASS_DT}
+ * @return the radio module class identifier.
+ */
+ public int getClassId() {
+ return mClassId;
+ }
+
+ /** Human readable broadcast radio module implementor
+ * @return the name of the radio module implementator.
+ */
+ public String getImplementor() {
+ return mImplementor;
+ }
+
+ /** Human readable broadcast radio module product name
+ * @return the radio module product name.
+ */
+ public String getProduct() {
+ return mProduct;
+ }
+
+ /** Human readable broadcast radio module version number
+ * @return the radio module version.
+ */
+ public String getVersion() {
+ return mVersion;
+ }
+
+ /** Radio module serial number.
+ * Can be used for subscription services.
+ * @return the radio module serial number.
+ */
+ public String getSerial() {
+ return mSerial;
+ }
+
+ /** Number of tuners available.
+ * This is the number of tuners that can be open simultaneously.
+ * @return the number of tuners supported.
+ */
+ public int getNumTuners() {
+ return mNumTuners;
+ }
+
+ /** Number tuner audio sources available. Must be less or equal to getNumTuners().
+ * When more than one tuner is supported, one is usually for playback and has one
+ * associated audio source and the other is for pre scanning and building a
+ * program list.
+ * @return the number of audio sources available.
+ */
+ public int getNumAudioSources() {
+ return mNumAudioSources;
+ }
+
+ /** {@code true} if audio capture is possible from radio tuner output.
+ * This indicates if routing to audio devices not connected to the same HAL as the FM radio
+ * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented.
+ * @return {@code true} if audio capture is possible, {@code false} otherwise.
+ */
+ public boolean isCaptureSupported() {
+ return mIsCaptureSupported;
+ }
+
+ /**
+ * {@code true} if the module supports background scanning. At the given time it may not
+ * be available though, see {@link RadioTuner#startBackgroundScan()}.
+ *
+ * @return {@code true} if background scanning is supported (not necessary available
+ * at a given time), {@code false} otherwise.
+ */
+ public boolean isBackgroundScanningSupported() {
+ return mIsBgScanSupported;
+ }
+
+ /**
+ * Checks, if a given program type is supported by this tuner.
+ *
+ * If a program type is supported by radio module, it means it can tune
+ * to ProgramSelector of a given type.
+ *
+ * @return {@code true} if a given program type is supported.
+ */
+ public boolean isProgramTypeSupported(@ProgramSelector.ProgramType int type) {
+ return mSupportedProgramTypes.contains(type);
+ }
+
+ /**
+ * Checks, if a given program identifier is supported by this tuner.
+ *
+ * If an identifier is supported by radio module, it means it can use it for
+ * tuning to ProgramSelector with either primary or secondary Identifier of
+ * a given type.
+ *
+ * @return {@code true} if a given program type is supported.
+ */
+ public boolean isProgramIdentifierSupported(@ProgramSelector.IdentifierType int type) {
+ return mSupportedIdentifierTypes.contains(type);
+ }
+
+ /**
+ * A map of vendor-specific opaque strings, passed from HAL without changes.
+ * Format of these strings can vary across vendors.
+ *
+ * It may be used for extra features, that's not supported by a platform,
+ * for example: preset-slots=6; ultra-hd-capable=false.
+ *
+ * Keys must be prefixed with unique vendor Java-style namespace,
+ * eg. 'com.somecompany.parameter1'.
+ */
+ public @NonNull Map<String, String> getVendorInfo() {
+ return mVendorInfo;
+ }
+
+ /** List of descriptors for all bands supported by this module.
+ * @return an array of {@link BandDescriptor}.
+ */
+ public BandDescriptor[] getBands() {
+ return mBands;
+ }
+
+ private ModuleProperties(Parcel in) {
+ mId = in.readInt();
+ String serviceName = in.readString();
+ mServiceName = TextUtils.isEmpty(serviceName) ? "default" : serviceName;
+ mClassId = in.readInt();
+ mImplementor = in.readString();
+ mProduct = in.readString();
+ mVersion = in.readString();
+ mSerial = in.readString();
+ mNumTuners = in.readInt();
+ mNumAudioSources = in.readInt();
+ mIsCaptureSupported = in.readInt() == 1;
+ Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader());
+ mBands = new BandDescriptor[tmp.length];
+ for (int i = 0; i < tmp.length; i++) {
+ mBands[i] = (BandDescriptor) tmp[i];
+ }
+ mIsBgScanSupported = in.readInt() == 1;
+ mSupportedProgramTypes = arrayToSet(in.createIntArray());
+ mSupportedIdentifierTypes = arrayToSet(in.createIntArray());
+ mVendorInfo = readStringMap(in);
+ }
+
+ public static final Parcelable.Creator<ModuleProperties> CREATOR
+ = new Parcelable.Creator<ModuleProperties>() {
+ public ModuleProperties createFromParcel(Parcel in) {
+ return new ModuleProperties(in);
+ }
+
+ public ModuleProperties[] newArray(int size) {
+ return new ModuleProperties[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeString(mServiceName);
+ dest.writeInt(mClassId);
+ dest.writeString(mImplementor);
+ dest.writeString(mProduct);
+ dest.writeString(mVersion);
+ dest.writeString(mSerial);
+ dest.writeInt(mNumTuners);
+ dest.writeInt(mNumAudioSources);
+ dest.writeInt(mIsCaptureSupported ? 1 : 0);
+ dest.writeParcelableArray(mBands, flags);
+ dest.writeInt(mIsBgScanSupported ? 1 : 0);
+ dest.writeIntArray(setToArray(mSupportedProgramTypes));
+ dest.writeIntArray(setToArray(mSupportedIdentifierTypes));
+ writeStringMap(dest, mVendorInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "ModuleProperties [mId=" + mId
+ + ", mServiceName=" + mServiceName + ", mClassId=" + mClassId
+ + ", mImplementor=" + mImplementor + ", mProduct=" + mProduct
+ + ", mVersion=" + mVersion + ", mSerial=" + mSerial
+ + ", mNumTuners=" + mNumTuners
+ + ", mNumAudioSources=" + mNumAudioSources
+ + ", mIsCaptureSupported=" + mIsCaptureSupported
+ + ", mIsBgScanSupported=" + mIsBgScanSupported
+ + ", mBands=" + Arrays.toString(mBands) + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mId;
+ result = prime * result + mServiceName.hashCode();
+ result = prime * result + mClassId;
+ result = prime * result + ((mImplementor == null) ? 0 : mImplementor.hashCode());
+ result = prime * result + ((mProduct == null) ? 0 : mProduct.hashCode());
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ result = prime * result + ((mSerial == null) ? 0 : mSerial.hashCode());
+ result = prime * result + mNumTuners;
+ result = prime * result + mNumAudioSources;
+ result = prime * result + (mIsCaptureSupported ? 1 : 0);
+ result = prime * result + Arrays.hashCode(mBands);
+ result = prime * result + (mIsBgScanSupported ? 1 : 0);
+ result = prime * result + mVendorInfo.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof ModuleProperties))
+ return false;
+ ModuleProperties other = (ModuleProperties) obj;
+ if (mId != other.getId())
+ return false;
+ if (!TextUtils.equals(mServiceName, other.mServiceName)) return false;
+ if (mClassId != other.getClassId())
+ return false;
+ if (mImplementor == null) {
+ if (other.getImplementor() != null)
+ return false;
+ } else if (!mImplementor.equals(other.getImplementor()))
+ return false;
+ if (mProduct == null) {
+ if (other.getProduct() != null)
+ return false;
+ } else if (!mProduct.equals(other.getProduct()))
+ return false;
+ if (mVersion == null) {
+ if (other.getVersion() != null)
+ return false;
+ } else if (!mVersion.equals(other.getVersion()))
+ return false;
+ if (mSerial == null) {
+ if (other.getSerial() != null)
+ return false;
+ } else if (!mSerial.equals(other.getSerial()))
+ return false;
+ if (mNumTuners != other.getNumTuners())
+ return false;
+ if (mNumAudioSources != other.getNumAudioSources())
+ return false;
+ if (mIsCaptureSupported != other.isCaptureSupported())
+ return false;
+ if (!Arrays.equals(mBands, other.getBands()))
+ return false;
+ if (mIsBgScanSupported != other.isBackgroundScanningSupported())
+ return false;
+ if (!mVendorInfo.equals(other.mVendorInfo)) return false;
+ return true;
+ }
+ }
+
+ /** Radio band descriptor: an element in ModuleProperties bands array.
+ * It is either an instance of {@link FmBandDescriptor} or {@link AmBandDescriptor} */
+ public static class BandDescriptor implements Parcelable {
+
+ private final int mRegion;
+ private final int mType;
+ private final int mLowerLimit;
+ private final int mUpperLimit;
+ private final int mSpacing;
+
+ BandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing) {
+ if (type != BAND_AM && type != BAND_FM && type != BAND_FM_HD && type != BAND_AM_HD) {
+ throw new IllegalArgumentException("Unsupported band: " + type);
+ }
+ mRegion = region;
+ mType = type;
+ mLowerLimit = lowerLimit;
+ mUpperLimit = upperLimit;
+ mSpacing = spacing;
+ }
+
+ /** Region this band applies to. E.g. {@link #REGION_ITU_1}
+ * @return the region this band is associated to.
+ */
+ public int getRegion() {
+ return mRegion;
+ }
+ /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+ * <ul>
+ * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
+ * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+ * </ul>
+ * @return the band type.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Checks if the band is either AM or AM_HD.
+ *
+ * @return {@code true}, if band is AM or AM_HD.
+ */
+ public boolean isAmBand() {
+ return mType == BAND_AM || mType == BAND_AM_HD;
+ }
+
+ /**
+ * Checks if the band is either FM or FM_HD.
+ *
+ * @return {@code true}, if band is FM or FM_HD.
+ */
+ public boolean isFmBand() {
+ return mType == BAND_FM || mType == BAND_FM_HD;
+ }
+
+ /** Lower band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the lower band limit.
+ */
+ public int getLowerLimit() {
+ return mLowerLimit;
+ }
+ /** Upper band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the upper band limit.
+ */
+ public int getUpperLimit() {
+ return mUpperLimit;
+ }
+ /** Channel spacing in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the channel spacing.
+ */
+ public int getSpacing() {
+ return mSpacing;
+ }
+
+ private BandDescriptor(Parcel in) {
+ mRegion = in.readInt();
+ mType = in.readInt();
+ mLowerLimit = in.readInt();
+ mUpperLimit = in.readInt();
+ mSpacing = in.readInt();
+ }
+
+ private static int lookupTypeFromParcel(Parcel in) {
+ int pos = in.dataPosition();
+ in.readInt(); // skip region
+ int type = in.readInt();
+ in.setDataPosition(pos);
+ return type;
+ }
+
+ public static final Parcelable.Creator<BandDescriptor> CREATOR
+ = new Parcelable.Creator<BandDescriptor>() {
+ public BandDescriptor createFromParcel(Parcel in) {
+ int type = lookupTypeFromParcel(in);
+ switch (type) {
+ case BAND_FM:
+ case BAND_FM_HD:
+ return new FmBandDescriptor(in);
+ case BAND_AM:
+ case BAND_AM_HD:
+ return new AmBandDescriptor(in);
+ default:
+ throw new IllegalArgumentException("Unsupported band: " + type);
+ }
+ }
+
+ public BandDescriptor[] newArray(int size) {
+ return new BandDescriptor[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRegion);
+ dest.writeInt(mType);
+ dest.writeInt(mLowerLimit);
+ dest.writeInt(mUpperLimit);
+ dest.writeInt(mSpacing);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit="
+ + mLowerLimit + ", mUpperLimit=" + mUpperLimit + ", mSpacing=" + mSpacing + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mRegion;
+ result = prime * result + mType;
+ result = prime * result + mLowerLimit;
+ result = prime * result + mUpperLimit;
+ result = prime * result + mSpacing;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof BandDescriptor))
+ return false;
+ BandDescriptor other = (BandDescriptor) obj;
+ if (mRegion != other.getRegion())
+ return false;
+ if (mType != other.getType())
+ return false;
+ if (mLowerLimit != other.getLowerLimit())
+ return false;
+ if (mUpperLimit != other.getUpperLimit())
+ return false;
+ if (mSpacing != other.getSpacing())
+ return false;
+ return true;
+ }
+ }
+
+ /** FM band descriptor
+ * @see #BAND_FM
+ * @see #BAND_FM_HD */
+ public static class FmBandDescriptor extends BandDescriptor {
+ private final boolean mStereo;
+ private final boolean mRds;
+ private final boolean mTa;
+ private final boolean mAf;
+ private final boolean mEa;
+
+ FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo, boolean rds, boolean ta, boolean af, boolean ea) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ mRds = rds;
+ mTa = ta;
+ mAf = af;
+ mEa = ea;
+ }
+
+ /** Stereo is supported
+ * @return {@code true} if stereo is supported, {@code false} otherwise.
+ */
+ public boolean isStereoSupported() {
+ return mStereo;
+ }
+ /** RDS or RBDS(if region is ITU2) is supported
+ * @return {@code true} if RDS or RBDS is supported, {@code false} otherwise.
+ */
+ public boolean isRdsSupported() {
+ return mRds;
+ }
+ /** Traffic announcement is supported
+ * @return {@code true} if TA is supported, {@code false} otherwise.
+ */
+ public boolean isTaSupported() {
+ return mTa;
+ }
+ /** Alternate Frequency Switching is supported
+ * @return {@code true} if AF switching is supported, {@code false} otherwise.
+ */
+ public boolean isAfSupported() {
+ return mAf;
+ }
+
+ /** Emergency Announcement is supported
+ * @return {@code true} if Emergency annoucement is supported, {@code false} otherwise.
+ */
+ public boolean isEaSupported() {
+ return mEa;
+ }
+
+ /* Parcelable implementation */
+ private FmBandDescriptor(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ mRds = in.readByte() == 1;
+ mTa = in.readByte() == 1;
+ mAf = in.readByte() == 1;
+ mEa = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<FmBandDescriptor> CREATOR
+ = new Parcelable.Creator<FmBandDescriptor>() {
+ public FmBandDescriptor createFromParcel(Parcel in) {
+ return new FmBandDescriptor(in);
+ }
+
+ public FmBandDescriptor[] newArray(int size) {
+ return new FmBandDescriptor[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ dest.writeByte((byte) (mRds ? 1 : 0));
+ dest.writeByte((byte) (mTa ? 1 : 0));
+ dest.writeByte((byte) (mAf ? 1 : 0));
+ dest.writeByte((byte) (mEa ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo
+ + ", mRds=" + mRds + ", mTa=" + mTa + ", mAf=" + mAf +
+ ", mEa =" + mEa + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ result = prime * result + (mRds ? 1 : 0);
+ result = prime * result + (mTa ? 1 : 0);
+ result = prime * result + (mAf ? 1 : 0);
+ result = prime * result + (mEa ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof FmBandDescriptor))
+ return false;
+ FmBandDescriptor other = (FmBandDescriptor) obj;
+ if (mStereo != other.isStereoSupported())
+ return false;
+ if (mRds != other.isRdsSupported())
+ return false;
+ if (mTa != other.isTaSupported())
+ return false;
+ if (mAf != other.isAfSupported())
+ return false;
+ if (mEa != other.isEaSupported())
+ return false;
+ return true;
+ }
+ }
+
+ /** AM band descriptor.
+ * @see #BAND_AM */
+ public static class AmBandDescriptor extends BandDescriptor {
+
+ private final boolean mStereo;
+
+ AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ }
+
+ /** Stereo is supported
+ * @return {@code true} if stereo is supported, {@code false} otherwise.
+ */
+ public boolean isStereoSupported() {
+ return mStereo;
+ }
+
+ private AmBandDescriptor(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<AmBandDescriptor> CREATOR
+ = new Parcelable.Creator<AmBandDescriptor>() {
+ public AmBandDescriptor createFromParcel(Parcel in) {
+ return new AmBandDescriptor(in);
+ }
+
+ public AmBandDescriptor[] newArray(int size) {
+ return new AmBandDescriptor[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof AmBandDescriptor))
+ return false;
+ AmBandDescriptor other = (AmBandDescriptor) obj;
+ if (mStereo != other.isStereoSupported())
+ return false;
+ return true;
+ }
+ }
+
+
+ /** Radio band configuration. */
+ public static class BandConfig implements Parcelable {
+
+ final BandDescriptor mDescriptor;
+
+ BandConfig(BandDescriptor descriptor) {
+ mDescriptor = descriptor;
+ }
+
+ BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) {
+ mDescriptor = new BandDescriptor(region, type, lowerLimit, upperLimit, spacing);
+ }
+
+ private BandConfig(Parcel in) {
+ mDescriptor = new BandDescriptor(in);
+ }
+
+ BandDescriptor getDescriptor() {
+ return mDescriptor;
+ }
+
+ /** Region this band applies to. E.g. {@link #REGION_ITU_1}
+ * @return the region associated with this band.
+ */
+ public int getRegion() {
+ return mDescriptor.getRegion();
+ }
+ /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+ * <ul>
+ * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
+ * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+ * </ul>
+ * @return the band type.
+ */
+ public int getType() {
+ return mDescriptor.getType();
+ }
+ /** Lower band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the lower band limit.
+ */
+ public int getLowerLimit() {
+ return mDescriptor.getLowerLimit();
+ }
+ /** Upper band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the upper band limit.
+ */
+ public int getUpperLimit() {
+ return mDescriptor.getUpperLimit();
+ }
+ /** Channel spacing in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the channel spacing.
+ */
+ public int getSpacing() {
+ return mDescriptor.getSpacing();
+ }
+
+
+ public static final Parcelable.Creator<BandConfig> CREATOR
+ = new Parcelable.Creator<BandConfig>() {
+ public BandConfig createFromParcel(Parcel in) {
+ int type = BandDescriptor.lookupTypeFromParcel(in);
+ switch (type) {
+ case BAND_FM:
+ case BAND_FM_HD:
+ return new FmBandConfig(in);
+ case BAND_AM:
+ case BAND_AM_HD:
+ return new AmBandConfig(in);
+ default:
+ throw new IllegalArgumentException("Unsupported band: " + type);
+ }
+ }
+
+ public BandConfig[] newArray(int size) {
+ return new BandConfig[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mDescriptor.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "BandConfig [ " + mDescriptor.toString() + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mDescriptor.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof BandConfig))
+ return false;
+ BandConfig other = (BandConfig) obj;
+ BandDescriptor otherDesc = other.getDescriptor();
+ if ((mDescriptor == null) != (otherDesc == null)) return false;
+ if (mDescriptor != null && !mDescriptor.equals(otherDesc)) return false;
+ return true;
+ }
+ }
+
+ /** FM band configuration.
+ * @see #BAND_FM
+ * @see #BAND_FM_HD */
+ public static class FmBandConfig extends BandConfig {
+ private final boolean mStereo;
+ private final boolean mRds;
+ private final boolean mTa;
+ private final boolean mAf;
+ private final boolean mEa;
+
+ FmBandConfig(FmBandDescriptor descriptor) {
+ super((BandDescriptor)descriptor);
+ mStereo = descriptor.isStereoSupported();
+ mRds = descriptor.isRdsSupported();
+ mTa = descriptor.isTaSupported();
+ mAf = descriptor.isAfSupported();
+ mEa = descriptor.isEaSupported();
+ }
+
+ FmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo, boolean rds, boolean ta, boolean af, boolean ea) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ mRds = rds;
+ mTa = ta;
+ mAf = af;
+ mEa = ea;
+ }
+
+ /** Get stereo enable state
+ * @return the enable state.
+ */
+ public boolean getStereo() {
+ return mStereo;
+ }
+
+ /** Get RDS or RBDS(if region is ITU2) enable state
+ * @return the enable state.
+ */
+ public boolean getRds() {
+ return mRds;
+ }
+
+ /** Get Traffic announcement enable state
+ * @return the enable state.
+ */
+ public boolean getTa() {
+ return mTa;
+ }
+
+ /** Get Alternate Frequency Switching enable state
+ * @return the enable state.
+ */
+ public boolean getAf() {
+ return mAf;
+ }
+
+ /**
+ * Get Emergency announcement enable state
+ * @return the enable state.
+ */
+ public boolean getEa() {
+ return mEa;
+ }
+
+ private FmBandConfig(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ mRds = in.readByte() == 1;
+ mTa = in.readByte() == 1;
+ mAf = in.readByte() == 1;
+ mEa = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<FmBandConfig> CREATOR
+ = new Parcelable.Creator<FmBandConfig>() {
+ public FmBandConfig createFromParcel(Parcel in) {
+ return new FmBandConfig(in);
+ }
+
+ public FmBandConfig[] newArray(int size) {
+ return new FmBandConfig[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ dest.writeByte((byte) (mRds ? 1 : 0));
+ dest.writeByte((byte) (mTa ? 1 : 0));
+ dest.writeByte((byte) (mAf ? 1 : 0));
+ dest.writeByte((byte) (mEa ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "FmBandConfig [" + super.toString()
+ + ", mStereo=" + mStereo + ", mRds=" + mRds + ", mTa=" + mTa
+ + ", mAf=" + mAf + ", mEa =" + mEa + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ result = prime * result + (mRds ? 1 : 0);
+ result = prime * result + (mTa ? 1 : 0);
+ result = prime * result + (mAf ? 1 : 0);
+ result = prime * result + (mEa ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof FmBandConfig))
+ return false;
+ FmBandConfig other = (FmBandConfig) obj;
+ if (mStereo != other.mStereo)
+ return false;
+ if (mRds != other.mRds)
+ return false;
+ if (mTa != other.mTa)
+ return false;
+ if (mAf != other.mAf)
+ return false;
+ if (mEa != other.mEa)
+ return false;
+ return true;
+ }
+
+ /**
+ * Builder class for {@link FmBandConfig} objects.
+ */
+ public static class Builder {
+ private final BandDescriptor mDescriptor;
+ private boolean mStereo;
+ private boolean mRds;
+ private boolean mTa;
+ private boolean mAf;
+ private boolean mEa;
+
+ /**
+ * Constructs a new Builder with the defaults from an {@link FmBandDescriptor} .
+ * @param descriptor the FmBandDescriptor defaults are read from .
+ */
+ public Builder(FmBandDescriptor descriptor) {
+ mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(),
+ descriptor.getLowerLimit(), descriptor.getUpperLimit(),
+ descriptor.getSpacing());
+ mStereo = descriptor.isStereoSupported();
+ mRds = descriptor.isRdsSupported();
+ mTa = descriptor.isTaSupported();
+ mAf = descriptor.isAfSupported();
+ mEa = descriptor.isEaSupported();
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link FmBandConfig}
+ * @param config the FmBandConfig object whose data will be reused in the new Builder.
+ */
+ public Builder(FmBandConfig config) {
+ mDescriptor = new BandDescriptor(config.getRegion(), config.getType(),
+ config.getLowerLimit(), config.getUpperLimit(), config.getSpacing());
+ mStereo = config.getStereo();
+ mRds = config.getRds();
+ mTa = config.getTa();
+ mAf = config.getAf();
+ mEa = config.getEa();
+ }
+
+ /**
+ * Combines all of the parameters that have been set and return a new
+ * {@link FmBandConfig} object.
+ * @return a new {@link FmBandConfig} object
+ */
+ public FmBandConfig build() {
+ FmBandConfig config = new FmBandConfig(mDescriptor.getRegion(),
+ mDescriptor.getType(), mDescriptor.getLowerLimit(),
+ mDescriptor.getUpperLimit(), mDescriptor.getSpacing(),
+ mStereo, mRds, mTa, mAf, mEa);
+ return config;
+ }
+
+ /** Set stereo enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setStereo(boolean state) {
+ mStereo = state;
+ return this;
+ }
+
+ /** Set RDS or RBDS(if region is ITU2) enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setRds(boolean state) {
+ mRds = state;
+ return this;
+ }
+
+ /** Set Traffic announcement enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setTa(boolean state) {
+ mTa = state;
+ return this;
+ }
+
+ /** Set Alternate Frequency Switching enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setAf(boolean state) {
+ mAf = state;
+ return this;
+ }
+
+ /** Set Emergency Announcement enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setEa(boolean state) {
+ mEa = state;
+ return this;
+ }
+ };
+ }
+
+ /** AM band configuration.
+ * @see #BAND_AM */
+ public static class AmBandConfig extends BandConfig {
+ private final boolean mStereo;
+
+ AmBandConfig(AmBandDescriptor descriptor) {
+ super((BandDescriptor)descriptor);
+ mStereo = descriptor.isStereoSupported();
+ }
+
+ AmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ }
+
+ /** Get stereo enable state
+ * @return the enable state.
+ */
+ public boolean getStereo() {
+ return mStereo;
+ }
+
+ private AmBandConfig(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<AmBandConfig> CREATOR
+ = new Parcelable.Creator<AmBandConfig>() {
+ public AmBandConfig createFromParcel(Parcel in) {
+ return new AmBandConfig(in);
+ }
+
+ public AmBandConfig[] newArray(int size) {
+ return new AmBandConfig[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "AmBandConfig [" + super.toString()
+ + ", mStereo=" + mStereo + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof AmBandConfig))
+ return false;
+ AmBandConfig other = (AmBandConfig) obj;
+ if (mStereo != other.getStereo())
+ return false;
+ return true;
+ }
+
+ /**
+ * Builder class for {@link AmBandConfig} objects.
+ */
+ public static class Builder {
+ private final BandDescriptor mDescriptor;
+ private boolean mStereo;
+
+ /**
+ * Constructs a new Builder with the defaults from an {@link AmBandDescriptor} .
+ * @param descriptor the FmBandDescriptor defaults are read from .
+ */
+ public Builder(AmBandDescriptor descriptor) {
+ mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(),
+ descriptor.getLowerLimit(), descriptor.getUpperLimit(),
+ descriptor.getSpacing());
+ mStereo = descriptor.isStereoSupported();
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link AmBandConfig}
+ * @param config the FmBandConfig object whose data will be reused in the new Builder.
+ */
+ public Builder(AmBandConfig config) {
+ mDescriptor = new BandDescriptor(config.getRegion(), config.getType(),
+ config.getLowerLimit(), config.getUpperLimit(), config.getSpacing());
+ mStereo = config.getStereo();
+ }
+
+ /**
+ * Combines all of the parameters that have been set and return a new
+ * {@link AmBandConfig} object.
+ * @return a new {@link AmBandConfig} object
+ */
+ public AmBandConfig build() {
+ AmBandConfig config = new AmBandConfig(mDescriptor.getRegion(),
+ mDescriptor.getType(), mDescriptor.getLowerLimit(),
+ mDescriptor.getUpperLimit(), mDescriptor.getSpacing(),
+ mStereo);
+ return config;
+ }
+
+ /** Set stereo enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setStereo(boolean state) {
+ mStereo = state;
+ return this;
+ }
+ };
+ }
+
+ /** Radio program information returned by
+ * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */
+ public static class ProgramInfo implements Parcelable {
+
+ // sourced from hardware/interfaces/broadcastradio/1.1/types.hal
+ private static final int FLAG_LIVE = 1 << 0;
+ private static final int FLAG_MUTED = 1 << 1;
+ private static final int FLAG_TRAFFIC_PROGRAM = 1 << 2;
+ private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
+
+ @NonNull private final ProgramSelector mSelector;
+ private final boolean mTuned;
+ private final boolean mStereo;
+ private final boolean mDigital;
+ private final int mFlags;
+ private final int mSignalStrength;
+ private final RadioMetadata mMetadata;
+ @NonNull private final Map<String, String> mVendorInfo;
+
+ ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
+ boolean digital, int signalStrength, RadioMetadata metadata, int flags,
+ Map<String, String> vendorInfo) {
+ mSelector = selector;
+ mTuned = tuned;
+ mStereo = stereo;
+ mDigital = digital;
+ mFlags = flags;
+ mSignalStrength = signalStrength;
+ mMetadata = metadata;
+ mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo;
+ }
+
+ /**
+ * 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() {
+ 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() {
+ 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.
+ */
+ public boolean isTuned() {
+ return mTuned;
+ }
+ /** {@code true} if the received program is stereo
+ * @return {@code true} if stereo, {@code false} otherwise.
+ */
+ public boolean isStereo() {
+ return mStereo;
+ }
+ /** {@code true} if the received program is digital (e.g HD radio)
+ * @return {@code true} if digital, {@code false} otherwise.
+ */
+ public boolean isDigital() {
+ return mDigital;
+ }
+
+ /**
+ * {@code true} if the program is currently playing live stream.
+ * This may result in a slightly altered reception parameters,
+ * usually targetted at reduced latency.
+ */
+ public boolean isLive() {
+ return (mFlags & FLAG_LIVE) != 0;
+ }
+
+ /**
+ * {@code true} if radio stream is not playing, ie. 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.
+ */
+ public boolean isMuted() {
+ return (mFlags & FLAG_MUTED) != 0;
+ }
+
+ /**
+ * {@code true} if radio station transmits traffic information
+ * regularily.
+ */
+ public boolean isTrafficProgram() {
+ return (mFlags & FLAG_TRAFFIC_PROGRAM) != 0;
+ }
+
+ /**
+ * {@code true} if radio station transmits traffic information
+ * at the very moment.
+ */
+ public boolean isTrafficAnnouncementActive() {
+ return (mFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0;
+ }
+
+ /** Signal strength indicator from 0 (no signal) to 100 (excellent)
+ * @return the signal strength indication.
+ */
+ public int getSignalStrength() {
+ return mSignalStrength;
+ }
+ /** Metadata currently received from this station.
+ * null if no metadata have been received
+ * @return current meta data received from this program.
+ */
+ public RadioMetadata getMetadata() {
+ return mMetadata;
+ }
+
+ /**
+ * A map of vendor-specific opaque strings, passed from HAL without changes.
+ * Format of these strings can vary across vendors.
+ *
+ * It may be used for extra features, that's not supported by a platform,
+ * for example: paid-service=true; bitrate=320kbps.
+ *
+ * Keys must be prefixed with unique vendor Java-style namespace,
+ * eg. 'com.somecompany.parameter1'.
+ */
+ public @NonNull Map<String, String> getVendorInfo() {
+ return mVendorInfo;
+ }
+
+ private ProgramInfo(Parcel in) {
+ mSelector = in.readParcelable(null);
+ mTuned = in.readByte() == 1;
+ mStereo = in.readByte() == 1;
+ mDigital = in.readByte() == 1;
+ mSignalStrength = in.readInt();
+ if (in.readByte() == 1) {
+ mMetadata = RadioMetadata.CREATOR.createFromParcel(in);
+ } else {
+ mMetadata = null;
+ }
+ mFlags = in.readInt();
+ mVendorInfo = readStringMap(in);
+ }
+
+ public static final Parcelable.Creator<ProgramInfo> CREATOR
+ = new Parcelable.Creator<ProgramInfo>() {
+ public ProgramInfo createFromParcel(Parcel in) {
+ return new ProgramInfo(in);
+ }
+
+ public ProgramInfo[] newArray(int size) {
+ return new ProgramInfo[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mSelector, 0);
+ dest.writeByte((byte)(mTuned ? 1 : 0));
+ dest.writeByte((byte)(mStereo ? 1 : 0));
+ dest.writeByte((byte)(mDigital ? 1 : 0));
+ dest.writeInt(mSignalStrength);
+ if (mMetadata == null) {
+ dest.writeByte((byte)0);
+ } else {
+ dest.writeByte((byte)1);
+ mMetadata.writeToParcel(dest, flags);
+ }
+ dest.writeInt(mFlags);
+ writeStringMap(dest, mVendorInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "ProgramInfo [mSelector=" + mSelector
+ + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
+ + ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength
+ + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
+ + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mSelector.hashCode();
+ result = prime * result + (mTuned ? 1 : 0);
+ result = prime * result + (mStereo ? 1 : 0);
+ result = prime * result + (mDigital ? 1 : 0);
+ result = prime * result + mFlags;
+ result = prime * result + mSignalStrength;
+ result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode());
+ result = prime * result + mVendorInfo.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof ProgramInfo))
+ return false;
+ ProgramInfo other = (ProgramInfo) obj;
+ if (!mSelector.equals(other.getSelector())) return false;
+ if (mTuned != other.isTuned())
+ return false;
+ if (mStereo != other.isStereo())
+ return false;
+ if (mDigital != other.isDigital())
+ return false;
+ if (mFlags != other.mFlags)
+ return false;
+ if (mSignalStrength != other.getSignalStrength())
+ return false;
+ if (mMetadata == null) {
+ if (other.getMetadata() != null)
+ return false;
+ } else if (!mMetadata.equals(other.getMetadata()))
+ return false;
+ if (!mVendorInfo.equals(other.mVendorInfo)) return false;
+ return true;
+ }
+ }
+
+
+ /**
+ * Returns a list of descriptors for all broadcast radio modules present on the device.
+ * @param modules An List of {@link ModuleProperties} where the list will be returned.
+ * @return
+ * <ul>
+ * <li>{@link #STATUS_OK} in case of success, </li>
+ * <li>{@link #STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link #STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link #STATUS_BAD_VALUE} if modules is null, </li>
+ * <li>{@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails, </li>
+ * </ul>
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public int listModules(List<ModuleProperties> modules) {
+ if (modules == null) {
+ Log.e(TAG, "the output list must not be empty");
+ return STATUS_BAD_VALUE;
+ }
+
+ Log.d(TAG, "Listing available tuners...");
+ List<ModuleProperties> returnedList;
+ try {
+ returnedList = mService.listModules();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed listing available tuners", e);
+ return STATUS_DEAD_OBJECT;
+ }
+
+ if (returnedList == null) {
+ Log.e(TAG, "Returned list was a null");
+ return STATUS_ERROR;
+ }
+
+ modules.addAll(returnedList);
+ return STATUS_OK;
+ }
+
+ private native int nativeListModules(List<ModuleProperties> modules);
+
+ /**
+ * Open an interface to control a tuner on a given broadcast radio module.
+ * Optionally selects and applies the configuration passed as "config" argument.
+ * @param moduleId radio module identifier {@link ModuleProperties#getId()}. Mandatory.
+ * @param config desired band and configuration to apply when enabling the hardware module.
+ * optional, can be null.
+ * @param withAudio {@code true} to request a tuner with an audio source.
+ * This tuner is intended for live listening or recording or a radio program.
+ * If {@code false}, the tuner can only be used to retrieve program informations.
+ * @param callback {@link RadioTuner.Callback} interface. Mandatory.
+ * @param handler the Handler on which the callbacks will be received.
+ * Can be null if default handler is OK.
+ * @return a valid {@link RadioTuner} interface in case of success or null in case of error.
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public RadioTuner openTuner(int moduleId, BandConfig config, boolean withAudio,
+ RadioTuner.Callback callback, Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be empty");
+ }
+
+ Log.d(TAG, "Opening tuner " + moduleId + "...");
+
+ ITuner tuner;
+ TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
+ try {
+ tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to open tuner", e);
+ return null;
+ }
+ if (tuner == null) {
+ Log.e(TAG, "Failed to open tuner");
+ return null;
+ }
+ return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID);
+ }
+
+ @NonNull private final Context mContext;
+ @NonNull private final IRadioService mService;
+
+ /**
+ * @hide
+ */
+ public RadioManager(@NonNull Context context) throws ServiceNotFoundException {
+ mContext = context;
+ mService = IRadioService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.RADIO_SERVICE));
+ }
+}
diff --git a/android/hardware/radio/RadioMetadata.java b/android/hardware/radio/RadioMetadata.java
new file mode 100644
index 00000000..3cc4b566
--- /dev/null
+++ b/android/hardware/radio/RadioMetadata.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) 2015 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.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.Set;
+
+/**
+ * Contains meta data about a radio program such as station name, song title, artist etc...
+ * @hide
+ */
+@SystemApi
+public final class RadioMetadata implements Parcelable {
+ private static final String TAG = "BroadcastRadio.metadata";
+
+ /**
+ * The RDS Program Information.
+ */
+ public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
+
+ /**
+ * The RDS Program Service.
+ */
+ public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
+
+ /**
+ * The RDS PTY.
+ */
+ public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
+
+ /**
+ * The RBDS PTY.
+ */
+ public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
+
+ /**
+ * The RBDS Radio Text.
+ */
+ public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
+
+ /**
+ * The song title.
+ */
+ public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
+
+ /**
+ * The artist name.
+ */
+ public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
+
+ /**
+ * The album name.
+ */
+ public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
+
+ /**
+ * The music genre.
+ */
+ public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
+
+ /**
+ * The radio station icon {@link Bitmap}.
+ */
+ public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
+
+ /**
+ * The artwork for the song/album {@link Bitmap}.
+ */
+ public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
+
+ /**
+ * The clock.
+ */
+ public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK";
+
+
+ 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 ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+ static {
+ METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK);
+ }
+
+ // keep in sync with: system/media/radio/include/system/radio_metadata.h
+ private static final int NATIVE_KEY_INVALID = -1;
+ private static final int NATIVE_KEY_RDS_PI = 0;
+ private static final int NATIVE_KEY_RDS_PS = 1;
+ private static final int NATIVE_KEY_RDS_PTY = 2;
+ private static final int NATIVE_KEY_RBDS_PTY = 3;
+ private static final int NATIVE_KEY_RDS_RT = 4;
+ private static final int NATIVE_KEY_TITLE = 5;
+ private static final int NATIVE_KEY_ARTIST = 6;
+ private static final int NATIVE_KEY_ALBUM = 7;
+ private static final int NATIVE_KEY_GENRE = 8;
+ private static final int NATIVE_KEY_ICON = 9;
+ private static final int NATIVE_KEY_ART = 10;
+ private static final int NATIVE_KEY_CLOCK = 11;
+
+ private static final SparseArray<String> NATIVE_KEY_MAPPING;
+
+ static {
+ NATIVE_KEY_MAPPING = new SparseArray<String>();
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK);
+ }
+
+ /**
+ * Provides a Clock that can be used to describe time as provided by the Radio.
+ *
+ * The clock is defined by the seconds since epoch at the UTC + 0 timezone
+ * and timezone offset from UTC + 0 represented in number of minutes.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Clock implements Parcelable {
+ private final long mUtcEpochSeconds;
+ private final int mTimezoneOffsetMinutes;
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mUtcEpochSeconds);
+ out.writeInt(mTimezoneOffsetMinutes);
+ }
+
+ public static final Parcelable.Creator<Clock> CREATOR
+ = new Parcelable.Creator<Clock>() {
+ public Clock createFromParcel(Parcel in) {
+ return new Clock(in);
+ }
+
+ public Clock[] newArray(int size) {
+ return new Clock[size];
+ }
+ };
+
+ public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) {
+ mUtcEpochSeconds = utcEpochSeconds;
+ mTimezoneOffsetMinutes = timezoneOffsetMinutes;
+ }
+
+ private Clock(Parcel in) {
+ mUtcEpochSeconds = in.readLong();
+ mTimezoneOffsetMinutes = in.readInt();
+ }
+
+ public long getUtcEpochSeconds() {
+ return mUtcEpochSeconds;
+ }
+
+ public int getTimezoneOffsetMinutes() {
+ return mTimezoneOffsetMinutes;
+ }
+ }
+
+ private final Bundle mBundle;
+
+ RadioMetadata() {
+ mBundle = new Bundle();
+ }
+
+ private RadioMetadata(Bundle bundle) {
+ mBundle = new Bundle(bundle);
+ }
+
+ private RadioMetadata(Parcel in) {
+ mBundle = in.readBundle();
+ }
+
+ /**
+ * Returns {@code true} if the given key is contained in the meta data
+ *
+ * @param key a String key
+ * @return {@code true} if the key exists in this meta data, {@code false} otherwise
+ */
+ public boolean containsKey(String key) {
+ return mBundle.containsKey(key);
+ }
+
+ /**
+ * Returns the text value associated with the given key as a String, or null
+ * if the key is not found in the meta data.
+ *
+ * @param key The key the value is stored under
+ * @return a String value, or null
+ */
+ public String getString(String key) {
+ return mBundle.getString(key);
+ }
+
+ private static void putInt(Bundle bundle, String key, int value) {
+ int type = METADATA_KEYS_TYPE.getOrDefault(key, METADATA_TYPE_INVALID);
+ if (type != METADATA_TYPE_INT && type != METADATA_TYPE_BITMAP) {
+ throw new IllegalArgumentException("The " + key + " key cannot be used to put an int");
+ }
+ bundle.putInt(key, value);
+ }
+
+ /**
+ * Returns the value associated with the given key,
+ * or 0 if the key is not found in the meta data.
+ *
+ * @param key The key the value is stored under
+ * @return an int value
+ */
+ public int getInt(String key) {
+ return mBundle.getInt(key, 0);
+ }
+
+ /**
+ * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
+ *
+ * @param key The key the value is stored under
+ * @return a {@link Bitmap} or null
+ * @deprecated Use getBitmapId(String) instead
+ */
+ @Deprecated
+ public Bitmap getBitmap(String key) {
+ Bitmap bmp = null;
+ try {
+ bmp = mBundle.getParcelable(key);
+ } catch (Exception e) {
+ // ignore, value was not a bitmap
+ Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
+ }
+ return bmp;
+ }
+
+ /**
+ * Retrieves an identifier for a bitmap.
+ *
+ * The format of an identifier is opaque to the application,
+ * with a special case of value 0 being invalid.
+ * An identifier for a given image-tuner pair is unique, so an application
+ * may cache images and determine if there is a necessity to fetch them
+ * again - if identifier changes, it means the image has changed.
+ *
+ * Only bitmap keys may be used with this method:
+ * <ul>
+ * <li>{@link #METADATA_KEY_ICON}</li>
+ * <li>{@link #METADATA_KEY_ART}</li>
+ * </ul>
+ *
+ * @param key The key the value is stored under.
+ * @return a bitmap identifier or 0 if it's missing.
+ * @hide This API is not thoroughly elaborated yet
+ */
+ public int getBitmapId(@NonNull String key) {
+ if (!METADATA_KEY_ICON.equals(key) && !METADATA_KEY_ART.equals(key)) return 0;
+ return getInt(key);
+ }
+
+ public Clock getClock(String key) {
+ Clock clock = null;
+ try {
+ clock = mBundle.getParcelable(key);
+ } catch (Exception e) {
+ // ignore, value was not a clock.
+ Log.w(TAG, "Failed to retrieve a key as Clock.", e);
+ }
+ return clock;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBundle(mBundle);
+ }
+
+ /**
+ * Returns the number of fields in this meta data.
+ *
+ * @return the number of fields in the meta data.
+ */
+ public int size() {
+ return mBundle.size();
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this meta data.
+ *
+ * @return a Set of String keys
+ */
+ public Set<String> keySet() {
+ return mBundle.keySet();
+ }
+
+ /**
+ * Helper for getting the String key used by {@link RadioMetadata} from the
+ * corrsponding native integer key.
+ *
+ * @param editorKey The key used by the editor
+ * @return the key used by this class or null if no mapping exists
+ * @hide
+ */
+ public static String getKeyFromNativeKey(int nativeKey) {
+ return NATIVE_KEY_MAPPING.get(nativeKey, null);
+ }
+
+ public static final Parcelable.Creator<RadioMetadata> CREATOR =
+ new Parcelable.Creator<RadioMetadata>() {
+ @Override
+ public RadioMetadata createFromParcel(Parcel in) {
+ return new RadioMetadata(in);
+ }
+
+ @Override
+ public RadioMetadata[] newArray(int size) {
+ return new RadioMetadata[size];
+ }
+ };
+
+ /**
+ * Use to build RadioMetadata objects.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+
+ /**
+ * Create an empty Builder. Any field that should be included in the
+ * {@link RadioMetadata} must be added.
+ */
+ public Builder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Create a Builder using a {@link RadioMetadata} instance to set the
+ * initial values. All fields in the source meta data will be included in
+ * the new meta data. Fields can be overwritten by adding the same key.
+ *
+ * @param source
+ */
+ public Builder(RadioMetadata source) {
+ mBundle = new Bundle(source.mBundle);
+ }
+
+ /**
+ * Create a Builder using a {@link RadioMetadata} instance to set
+ * initial values, but replace bitmaps with a scaled down copy if they
+ * are larger than maxBitmapSize.
+ *
+ * @param source The original meta data to copy.
+ * @param maxBitmapSize The maximum height/width for bitmaps contained
+ * in the meta data.
+ * @hide
+ */
+ public Builder(RadioMetadata source, int maxBitmapSize) {
+ this(source);
+ for (String key : mBundle.keySet()) {
+ Object value = mBundle.get(key);
+ if (value != null && value instanceof Bitmap) {
+ Bitmap bmp = (Bitmap) value;
+ if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
+ putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
+ }
+ }
+ }
+ }
+
+ /**
+ * Put a String value 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_RDS_PS}</li>
+ * <li>{@link #METADATA_KEY_RDS_RT}</li>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return the same Builder instance
+ */
+ public Builder putString(String key, String value) {
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a String");
+ }
+ mBundle.putString(key, value);
+ return this;
+ }
+
+ /**
+ * Put an int value 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_RDS_PI}</li>
+ * <li>{@link #METADATA_KEY_RDS_PTY}</li>
+ * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
+ * </ul>
+ * or any bitmap represented by its identifier.
+ *
+ * @param key The key for referencing this value
+ * @param value The int value to store
+ * @return the same Builder instance
+ */
+ public Builder putInt(String key, int value) {
+ RadioMetadata.putInt(mBundle, key, value);
+ return this;
+ }
+
+ /**
+ * Put a {@link Bitmap} 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_ICON}</li>
+ * <li>{@link #METADATA_KEY_ART}</li>
+ * </ul>
+ * <p>
+ *
+ * @param key The key for referencing this value
+ * @param value The Bitmap to store
+ * @return the same Builder instance
+ */
+ public Builder putBitmap(String key, Bitmap value) {
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Bitmap");
+ }
+ mBundle.putParcelable(key, value);
+ return this;
+ }
+
+ /**
+ * Put a {@link RadioMetadata.Clock} 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 #MEADATA_KEY_CLOCK}</li>
+ * </ul>
+ *
+ * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
+ * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
+ * @return the same Builder instance.
+ */
+ public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a RadioMetadata.Clock.");
+ }
+ mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes));
+ return this;
+ }
+
+ /**
+ * Creates a {@link RadioMetadata} instance with the specified fields.
+ *
+ * @return a new {@link RadioMetadata} object
+ */
+ public RadioMetadata build() {
+ return new RadioMetadata(mBundle);
+ }
+
+ private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
+ float maxSizeF = maxSize;
+ float widthScale = maxSizeF / bmp.getWidth();
+ float heightScale = maxSizeF / bmp.getHeight();
+ float scale = Math.min(widthScale, heightScale);
+ int height = (int) (bmp.getHeight() * scale);
+ int width = (int) (bmp.getWidth() * scale);
+ return Bitmap.createScaledBitmap(bmp, width, height, true);
+ }
+ }
+
+ int putIntFromNative(int nativeKey, int value) {
+ String key = getKeyFromNativeKey(nativeKey);
+ try {
+ putInt(mBundle, key, value);
+ return 0;
+ } catch (IllegalArgumentException ex) {
+ return -1;
+ }
+ }
+
+ int putStringFromNative(int nativeKey, String value) {
+ String key = getKeyFromNativeKey(nativeKey);
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ return -1;
+ }
+ mBundle.putString(key, value);
+ return 0;
+ }
+
+ int putBitmapFromNative(int nativeKey, byte[] value) {
+ String key = getKeyFromNativeKey(nativeKey);
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+ return -1;
+ }
+ Bitmap bmp = null;
+ try {
+ bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
+ if (bmp != null) {
+ mBundle.putParcelable(key, bmp);
+ return 0;
+ }
+ } catch (Exception e) {
+ }
+ return -1;
+ }
+
+ int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
+ String key = getKeyFromNativeKey(nativeKey);
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
+ return -1;
+ }
+ mBundle.putParcelable(key, new RadioMetadata.Clock(
+ utcEpochSeconds, timezoneOffsetInMinutes));
+ return 0;
+ }
+}
diff --git a/android/hardware/radio/RadioTuner.java b/android/hardware/radio/RadioTuner.java
new file mode 100644
index 00000000..6e8991aa
--- /dev/null
+++ b/android/hardware/radio/RadioTuner.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2015 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.os.Handler;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RadioTuner interface provides methods to control a radio tuner on the device: selecting and
+ * configuring the active band, muting/unmuting, scanning and tuning, etc...
+ *
+ * Obtain a RadioTuner interface by calling {@link RadioManager#openTuner(int,
+ * RadioManager.BandConfig, boolean, RadioTuner.Callback, Handler)}.
+ * @hide
+ */
+@SystemApi
+public abstract class RadioTuner {
+
+ /** Scanning direction UP for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
+ public static final int DIRECTION_UP = 0;
+
+ /** Scanning directions DOWN for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
+ public static final int DIRECTION_DOWN = 1;
+
+ /**
+ * Close the tuner interface. The {@link Callback} callback will not be called
+ * anymore and associated resources will be released.
+ * Must be called when the tuner is not needed to make hardware resources available to others.
+ * */
+ public abstract void close();
+
+ /**
+ * Set the active band configuration for this module.
+ * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed
+ * in the ModuleProperties of the module with the specified ID.
+ * @param config The desired band configuration (FmBandConfig or AmBandConfig).
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int setConfiguration(RadioManager.BandConfig config);
+
+ /**
+ * Get current configuration.
+ * @param config a BandConfig array of lengh 1 where the configuration is returned.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int getConfiguration(RadioManager.BandConfig[] config);
+
+
+ /**
+ * Set mute state. When muted, the radio tuner audio source is not available for playback on
+ * any audio device. when unmuted, the radio tuner audio source is output as a media source
+ * and renderd over the audio device selected for media use case.
+ * The radio tuner audio source is muted by default when the tuner is first attached.
+ * Only effective if the tuner is attached with audio enabled.
+ *
+ * @param mute the requested mute state.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int setMute(boolean mute);
+
+ /**
+ * Get mute state.
+ *
+ * @return {@code true} if the radio tuner audio source is muted or a problem occured
+ * retrieving the mute state, {@code false} otherwise.
+ */
+ public abstract boolean getMute();
+
+ /**
+ * Step up or down by one channel spacing.
+ * The operation is asynchronous and {@link Callback}
+ * onProgramInfoChanged() will be called when step completes or
+ * onError() when cancelled or timeout.
+ * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+ * @param skipSubChannel indicates to skip sub channels when the configuration currently
+ * selected supports sub channel (e.g HD Radio). N/A otherwise.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int step(int direction, boolean skipSubChannel);
+
+ /**
+ * Scan up or down to next valid station.
+ * The operation is asynchronous and {@link Callback}
+ * onProgramInfoChanged() will be called when scan completes or
+ * onError() when cancelled or timeout.
+ * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+ * @param skipSubChannel indicates to skip sub channels when the configuration currently
+ * selected supports sub channel (e.g HD Radio). N/A otherwise.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int scan(int direction, boolean skipSubChannel);
+
+ /**
+ * Tune to a specific frequency.
+ * The operation is asynchronous and {@link Callback}
+ * onProgramInfoChanged() will be called when tune completes or
+ * onError() when cancelled or timeout.
+ * @param channel the specific channel or frequency to tune to.
+ * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration
+ * does not support cub channels.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <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.
+ *
+ * @throws 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}.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int cancel();
+
+ /**
+ * Cancels traffic or emergency announcement.
+ *
+ * If there was no announcement to cancel, no action is taken.
+ *
+ * There is a race condition between calling cancelAnnouncement and the actual announcement
+ * being finished, so onTrafficAnnouncement / onEmergencyAnnouncement callback should be
+ * tracked with proper locking.
+ */
+ public abstract void cancelAnnouncement();
+
+ /**
+ * Get current station information.
+ * @param info a ProgramInfo array of lengh 1 where the information is returned.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
+
+ /**
+ * Retrieves a {@link Bitmap} for the given image ID or null,
+ * if the image was missing from the tuner.
+ *
+ * This involves doing a call to the tuner, so the bitmap should be cached
+ * on the application side.
+ *
+ * If the method returns null for non-zero ID, it means the image was
+ * updated on the tuner side. There is a race conditon between fetching
+ * image for an old ID and tuner updating the image (and cleaning up the
+ * old image). In such case, a new ProgramInfo with updated image id will
+ * be sent with a {@link onProgramInfoChanged} callback.
+ *
+ * @param id The image identifier, retrieved with
+ * {@link RadioMetadata#getBitmapId(String)}.
+ * @return A {@link Bitmap} or null.
+ * @throws IllegalArgumentException if id==0
+ * @hide This API is not thoroughly elaborated yet
+ */
+ public abstract @Nullable Bitmap getMetadataImage(int id);
+
+ /**
+ * Initiates a background scan to update internally cached program list.
+ *
+ * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
+ *
+ * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
+ * be called if the return value of this call was {@code true}. As result of this call
+ * programListChanged may be triggered (if the scanned list differs).
+ *
+ * @return {@code true} if the scan was properly scheduled, {@code false} if the scan feature
+ * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
+ * or permanently if the feature is not supported
+ * (see ModuleProperties#isBackgroundScanningSupported()).
+ */
+ public abstract boolean startBackgroundScan();
+
+ /**
+ * Get the list of discovered radio stations.
+ *
+ * To get the full list, set filter to null or empty map.
+ * Keys must be prefixed with unique vendor Java-style namespace,
+ * eg. 'com.somecompany.parameter1'.
+ *
+ * @param vendorFilter vendor-specific selector for radio stations.
+ * @return a list of radio stations.
+ * @throws IllegalStateException if the scan is in progress or has not been started,
+ * startBackgroundScan() call may fix it.
+ * @throws IllegalArgumentException if the vendorFilter argument is not valid.
+ */
+ public abstract @NonNull List<RadioManager.ProgramInfo>
+ getProgramList(@Nullable Map<String, String> vendorFilter);
+
+ /**
+ * Checks, if the analog playback is forced, see setAnalogForced.
+ *
+ * @throws IllegalStateException if the switch is not supported at current
+ * configuration.
+ * @return {@code true} if analog is forced, {@code false} otherwise.
+ */
+ public abstract boolean isAnalogForced();
+
+ /**
+ * 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-
+ * analog handover managed from the HAL implementation side.
+ *
+ * Some radio technologies may not support this, ie. DAB.
+ *
+ * @param isForced {@code true} to force analog, {@code false} for a default behaviour.
+ * @throws IllegalStateException if the switch is not supported at current
+ * configuration.
+ */
+ public abstract void setAnalogForced(boolean isForced);
+
+ /**
+ * Get current antenna connection state for current configuration.
+ * Only valid if a configuration has been applied.
+ * @return {@code true} if the antenna is connected, {@code false} otherwise.
+ */
+ public abstract boolean isAntennaConnected();
+
+ /**
+ * Indicates if this client actually controls the tuner.
+ * Control is always granted after
+ * {@link RadioManager#openTuner(int,
+ * RadioManager.BandConfig, boolean, Callback, Handler)}
+ * returns a non null tuner interface.
+ * Control is lost when another client opens an interface on the same tuner.
+ * When this happens, {@link Callback#onControlChanged(boolean)} is received.
+ * The client can either wait for control to be returned (which is indicated by the same
+ * callback) or close and reopen the tuner interface.
+ * @return {@code true} if this interface controls the tuner,
+ * {@code false} otherwise or if a problem occured retrieving the state.
+ */
+ public abstract boolean hasControl();
+
+ /** Indicates a failure of radio IC or driver.
+ * The application must close and re open the tuner */
+ public static final int ERROR_HARDWARE_FAILURE = 0;
+ /** Indicates a failure of the radio service.
+ * The application must close and re open the tuner */
+ public static final int ERROR_SERVER_DIED = 1;
+ /** A pending seek or tune operation was cancelled */
+ public static final int ERROR_CANCELLED = 2;
+ /** A pending seek or tune operation timed out */
+ public static final int ERROR_SCAN_TIMEOUT = 3;
+ /** The requested configuration could not be applied */
+ public static final int ERROR_CONFIG = 4;
+ /** Background scan was interrupted due to hardware becoming temporarily unavailable. */
+ public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5;
+ /** Background scan failed due to other error, ie. HW failure. */
+ public static final int ERROR_BACKGROUND_SCAN_FAILED = 6;
+
+ /**
+ * Callback provided by the client application when opening a {@link RadioTuner}
+ * to receive asynchronous operation results, updates and error notifications.
+ */
+ public static abstract class Callback {
+ /**
+ * onError() is called when an error occured while performing an asynchronous
+ * operation of when the hardware or system service experiences a problem.
+ * status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED},
+ * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
+ * {@link #ERROR_CONFIG}
+ */
+ public void onError(int status) {}
+ /**
+ * onConfigurationChanged() is called upon successful completion of
+ * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}
+ * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)}
+ */
+ public void onConfigurationChanged(RadioManager.BandConfig config) {}
+
+ /**
+ * Called when program info (including metadata) for the current program has changed.
+ *
+ * It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
+ * {@link RadioTuner#scan(int, boolean)}, {@link RadioTuner#tune(int, int)}; when
+ * a switching to alternate frequency occurs; or when metadata is updated.
+ */
+ public void onProgramInfoChanged(RadioManager.ProgramInfo info) {}
+
+ /**
+ * Called when metadata is updated for the current program.
+ *
+ * @deprecated Use {@link #onProgramInfoChanged(RadioManager.ProgramInfo)} instead.
+ */
+ @Deprecated
+ public void onMetadataChanged(RadioMetadata metadata) {}
+
+ /**
+ * onTrafficAnnouncement() is called when a traffic announcement starts and stops.
+ */
+ public void onTrafficAnnouncement(boolean active) {}
+ /**
+ * onEmergencyAnnouncement() is called when an emergency annoucement starts and stops.
+ */
+ public void onEmergencyAnnouncement(boolean active) {}
+ /**
+ * onAntennaState() is called when the antenna is connected or disconnected.
+ */
+ public void onAntennaState(boolean connected) {}
+ /**
+ * onControlChanged() is called when the client loses or gains control of the radio tuner.
+ * The control is always granted after a successful call to
+ * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}.
+ * If another client opens the same tuner, onControlChanged() will be called with
+ * control set to {@code false} to indicate loss of control.
+ * At this point, RadioTuner APIs other than getters will return
+ * {@link RadioManager#STATUS_INVALID_OPERATION}.
+ * When the other client releases the tuner, onControlChanged() will be called
+ * with control set to {@code true}.
+ */
+ public void onControlChanged(boolean control) {}
+
+ /**
+ * onBackgroundScanAvailabilityChange() is called when background scan
+ * feature becomes available or not.
+ *
+ * @param isAvailable true, if the tuner turned temporarily background-
+ * capable, false in the other case.
+ */
+ public void onBackgroundScanAvailabilityChange(boolean isAvailable) {}
+
+ /**
+ * Called when a background scan completes successfully.
+ */
+ public void onBackgroundScanComplete() {}
+
+ /**
+ * Called when available program list changed.
+ *
+ * Use {@link RadioTuner#getProgramList(String)} to get an actual list.
+ */
+ public void onProgramListChanged() {}
+ }
+
+}
+
diff --git a/android/hardware/radio/TunerAdapter.java b/android/hardware/radio/TunerAdapter.java
new file mode 100644
index 00000000..b6219690
--- /dev/null
+++ b/android/hardware/radio/TunerAdapter.java
@@ -0,0 +1,271 @@
+/**
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implements the RadioTuner interface by forwarding calls to radio service.
+ */
+class TunerAdapter extends RadioTuner {
+ private static final String TAG = "BroadcastRadio.TunerAdapter";
+
+ @NonNull private final ITuner mTuner;
+ private boolean mIsClosed = false;
+
+ private @RadioManager.Band int mBand;
+
+ TunerAdapter(ITuner tuner, @RadioManager.Band int band) {
+ if (tuner == null) {
+ throw new NullPointerException();
+ }
+ mTuner = tuner;
+ mBand = band;
+ }
+
+ @Override
+ public void close() {
+ synchronized (mTuner) {
+ if (mIsClosed) {
+ Log.v(TAG, "Tuner is already closed");
+ return;
+ }
+ mIsClosed = true;
+ }
+ try {
+ mTuner.close();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception trying to close tuner", e);
+ }
+ }
+
+ @Override
+ 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);
+ return RadioManager.STATUS_BAD_VALUE;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ }
+
+ @Override
+ public int getConfiguration(RadioManager.BandConfig[] config) {
+ if (config == null || config.length != 1) {
+ throw new IllegalArgumentException("The argument must be an array of length 1");
+ }
+ try {
+ config[0] = mTuner.getConfiguration();
+ return RadioManager.STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ }
+
+ @Override
+ public int setMute(boolean mute) {
+ try {
+ mTuner.setMuted(mute);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't set muted", e);
+ return RadioManager.STATUS_ERROR;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public boolean getMute() {
+ try {
+ return mTuner.isMuted();
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return true;
+ }
+ }
+
+ @Override
+ public int step(int direction, boolean skipSubChannel) {
+ try {
+ mTuner.step(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't step", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public int scan(int direction, boolean skipSubChannel) {
+ try {
+ mTuner.scan(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't scan", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public int tune(int channel, int subChannel) {
+ try {
+ mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel));
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't tune", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Can't tune", e);
+ return RadioManager.STATUS_BAD_VALUE;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @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();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't cancel", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public void cancelAnnouncement() {
+ try {
+ mTuner.cancelAnnouncement();
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public int getProgramInformation(RadioManager.ProgramInfo[] info) {
+ if (info == null || info.length != 1) {
+ throw new IllegalArgumentException("The argument must be an array of length 1");
+ }
+ try {
+ info[0] = mTuner.getProgramInformation();
+ return RadioManager.STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ }
+
+ @Override
+ public @Nullable Bitmap getMetadataImage(int id) {
+ try {
+ return mTuner.getImage(id);
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public boolean startBackgroundScan() {
+ try {
+ return mTuner.startBackgroundScan();
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public @NonNull List<RadioManager.ProgramInfo>
+ getProgramList(@Nullable Map<String, String> vendorFilter) {
+ try {
+ return mTuner.getProgramList(vendorFilter);
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public boolean isAnalogForced() {
+ try {
+ return mTuner.isAnalogForced();
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public void setAnalogForced(boolean isForced) {
+ try {
+ mTuner.setAnalogForced(isForced);
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public boolean isAntennaConnected() {
+ try {
+ return mTuner.isAntennaConnected();
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public boolean hasControl() {
+ try {
+ // don't rely on mIsClosed, as tuner might get closed internally
+ return !mTuner.isClosed();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+}
diff --git a/android/hardware/radio/TunerCallbackAdapter.java b/android/hardware/radio/TunerCallbackAdapter.java
new file mode 100644
index 00000000..ffd5b30f
--- /dev/null
+++ b/android/hardware/radio/TunerCallbackAdapter.java
@@ -0,0 +1,97 @@
+/**
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+/**
+ * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
+ */
+class TunerCallbackAdapter extends ITunerCallback.Stub {
+ private static final String TAG = "BroadcastRadio.TunerCallbackAdapter";
+
+ @NonNull private final RadioTuner.Callback mCallback;
+ @NonNull private final Handler mHandler;
+
+ TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
+ mCallback = callback;
+ if (handler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ } else {
+ mHandler = handler;
+ }
+ }
+
+ @Override
+ public void onError(int status) {
+ mHandler.post(() -> mCallback.onError(status));
+ }
+
+ @Override
+ public void onConfigurationChanged(RadioManager.BandConfig config) {
+ mHandler.post(() -> mCallback.onConfigurationChanged(config));
+ }
+
+ @Override
+ public void onCurrentProgramInfoChanged(RadioManager.ProgramInfo info) {
+ if (info == null) {
+ Log.e(TAG, "ProgramInfo must not be null");
+ return;
+ }
+
+ mHandler.post(() -> {
+ mCallback.onProgramInfoChanged(info);
+
+ RadioMetadata metadata = info.getMetadata();
+ if (metadata != null) mCallback.onMetadataChanged(metadata);
+ });
+ }
+
+ @Override
+ public void onTrafficAnnouncement(boolean active) {
+ mHandler.post(() -> mCallback.onTrafficAnnouncement(active));
+ }
+
+ @Override
+ public void onEmergencyAnnouncement(boolean active) {
+ mHandler.post(() -> mCallback.onEmergencyAnnouncement(active));
+ }
+
+ @Override
+ public void onAntennaState(boolean connected) {
+ mHandler.post(() -> mCallback.onAntennaState(connected));
+ }
+
+ @Override
+ public void onBackgroundScanAvailabilityChange(boolean isAvailable) {
+ mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable));
+ }
+
+ @Override
+ public void onBackgroundScanComplete() {
+ mHandler.post(() -> mCallback.onBackgroundScanComplete());
+ }
+
+ @Override
+ public void onProgramListChanged() {
+ mHandler.post(() -> mCallback.onProgramListChanged());
+ }
+}