/** * 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.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.content.pm.PackageManager; 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 com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; 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) @RequiresFeature(PackageManager.FEATURE_BROADCAST_RADIO) 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; /** * Forces mono audio stream reception. * * Analog broadcasts can recover poor reception conditions by jointing * stereo channels into one. Mainly for, but not limited to AM/FM. */ public static final int CONFIG_FORCE_MONO = 1; /** * 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 state managed from the HAL implementation side. * * Some radio technologies may not support this, ie. DAB. */ public static final int CONFIG_FORCE_ANALOG = 2; /** * Forces the digital playback for the supporting radio technology. * * User may disable digital-analog handover that happens with poor * reception conditions. With digital forced, the radio will remain silent * instead of switching to analog channel if it's available. This is purely * user choice, it does not reflect the actual state of handover. */ public static final int CONFIG_FORCE_DIGITAL = 3; /** * RDS Alternative Frequencies. * * If set and the currently tuned RDS station broadcasts on multiple * channels, radio tuner automatically switches to the best available * alternative. */ public static final int CONFIG_RDS_AF = 4; /** * RDS region-specific program lock-down. * * Allows user to lock to the current region as they move into the * other region. */ public static final int CONFIG_RDS_REG = 5; /** Enables DAB-DAB hard- and implicit-linking (the same content). */ public static final int CONFIG_DAB_DAB_LINKING = 6; /** Enables DAB-FM hard- and implicit-linking (the same content). */ public static final int CONFIG_DAB_FM_LINKING = 7; /** Enables DAB-DAB soft-linking (related content). */ public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; /** Enables DAB-FM soft-linking (related content). */ public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; /** @hide */ @IntDef(prefix = { "CONFIG_" }, value = { CONFIG_FORCE_MONO, CONFIG_FORCE_ANALOG, CONFIG_FORCE_DIGITAL, CONFIG_RDS_AF, CONFIG_RDS_REG, CONFIG_DAB_DAB_LINKING, CONFIG_DAB_FM_LINKING, CONFIG_DAB_DAB_SOFT_LINKING, CONFIG_DAB_FM_SOFT_LINKING, }) @Retention(RetentionPolicy.SOURCE) public @interface ConfigFlag {} /***************************************************************************** * 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 )} 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 mIsInitializationRequired; private final boolean mIsCaptureSupported; private final BandDescriptor[] mBands; private final boolean mIsBgScanSupported; private final Set mSupportedProgramTypes; private final Set mSupportedIdentifierTypes; @Nullable private final Map mDabFrequencyTable; @NonNull private final Map mVendorInfo; /** @hide */ public ModuleProperties(int id, String serviceName, int classId, String implementor, String product, String version, String serial, int numTuners, int numAudioSources, boolean isInitializationRequired, boolean isCaptureSupported, BandDescriptor[] bands, boolean isBgScanSupported, @ProgramSelector.ProgramType int[] supportedProgramTypes, @ProgramSelector.IdentifierType int[] supportedIdentifierTypes, @Nullable Map dabFrequencyTable, Map vendorInfo) { mId = id; mServiceName = TextUtils.isEmpty(serviceName) ? "default" : serviceName; mClassId = classId; mImplementor = implementor; mProduct = product; mVersion = version; mSerial = serial; mNumTuners = numTuners; mNumAudioSources = numAudioSources; mIsInitializationRequired = isInitializationRequired; mIsCaptureSupported = isCaptureSupported; mBands = bands; mIsBgScanSupported = isBgScanSupported; mSupportedProgramTypes = arrayToSet(supportedProgramTypes); mSupportedIdentifierTypes = arrayToSet(supportedIdentifierTypes); if (dabFrequencyTable != null) { for (Map.Entry entry : dabFrequencyTable.entrySet()) { Objects.requireNonNull(entry.getKey()); Objects.requireNonNull(entry.getValue()); } } mDabFrequencyTable = dabFrequencyTable; mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo; } private static Set arrayToSet(int[] arr) { return Arrays.stream(arr).boxed().collect(Collectors.toSet()); } private static int[] setToArray(Set 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; } /** * Checks, if BandConfig initialization (after {@link RadioManager#openTuner}) * is required to be done before other operations or not. * * If it is, the client has to wait for {@link RadioTuner.Callback#onConfigurationChanged} * callback before executing any other operations. Otherwise, such operation will fail * returning {@link RadioManager#STATUS_INVALID_OPERATION} error code. */ public boolean isInitializationRequired() { return mIsInitializationRequired; } /** {@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 frequency table for Digital Audio Broadcasting (DAB). * * The key is a channel name, i.e. 5A, 7B. * * The value is a frequency, in kHz. * * @return a frequency table, or {@code null} if the module doesn't support DAB */ public @Nullable Map getDabFrequencyTable() { return mDabFrequencyTable; } /** * 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 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(); mIsInitializationRequired = in.readInt() == 1; 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()); mDabFrequencyTable = Utils.readStringIntMap(in); mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 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(mIsInitializationRequired ? 1 : 0); dest.writeInt(mIsCaptureSupported ? 1 : 0); dest.writeParcelableArray(mBands, flags); dest.writeInt(mIsBgScanSupported ? 1 : 0); dest.writeIntArray(setToArray(mSupportedProgramTypes)); dest.writeIntArray(setToArray(mSupportedIdentifierTypes)); Utils.writeStringIntMap(dest, mDabFrequencyTable); Utils.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 + ", mIsInitializationRequired=" + mIsInitializationRequired + ", mIsCaptureSupported=" + mIsCaptureSupported + ", mIsBgScanSupported=" + mIsBgScanSupported + ", mBands=" + Arrays.toString(mBands) + "]"; } @Override public int hashCode() { return Objects.hash(mId, mServiceName, mClassId, mImplementor, mProduct, mVersion, mSerial, mNumTuners, mNumAudioSources, mIsInitializationRequired, mIsCaptureSupported, mBands, mIsBgScanSupported, mDabFrequencyTable, mVendorInfo); } @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.mClassId) return false; if (!Objects.equals(mImplementor, other.mImplementor)) return false; if (!Objects.equals(mProduct, other.mProduct)) return false; if (!Objects.equals(mVersion, other.mVersion)) return false; if (!Objects.equals(mSerial, other.mSerial)) return false; if (mNumTuners != other.mNumTuners) return false; if (mNumAudioSources != other.mNumAudioSources) return false; if (mIsInitializationRequired != other.mIsInitializationRequired) return false; if (mIsCaptureSupported != other.mIsCaptureSupported) return false; if (!Objects.equals(mBands, other.mBands)) return false; if (mIsBgScanSupported != other.mIsBgScanSupported) return false; if (!Objects.equals(mDabFrequencyTable, other.mDabFrequencyTable)) return false; if (!Objects.equals(mVendorInfo, 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: *
    *
  • {@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor},
  • *
  • {@link #BAND_AM} cast to {@link AmBandDescriptor},
  • *
* @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 CREATOR = new Parcelable.Creator() { 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; /** @hide */ public 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 CREATOR = new Parcelable.Creator() { 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; /** @hide */ public 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 CREATOR = new Parcelable.Creator() { 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 { @NonNull final BandDescriptor mDescriptor; BandConfig(BandDescriptor descriptor) { mDescriptor = Objects.requireNonNull(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: *
    *
  • {@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor},
  • *
  • {@link #BAND_AM} cast to {@link AmBandDescriptor},
  • *
* @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 CREATOR = new Parcelable.Creator() { 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; /** @hide */ public 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 CREATOR = new Parcelable.Creator() { 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; /** @hide */ public 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 CREATOR = new Parcelable.Creator() { 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. */ public static class ProgramInfo implements Parcelable { // sourced from hardware/interfaces/broadcastradio/2.0/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; private static final int FLAG_TUNED = 1 << 4; private static final int FLAG_STEREO = 1 << 5; @NonNull private final ProgramSelector mSelector; @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo; @Nullable private final ProgramSelector.Identifier mPhysicallyTunedTo; @NonNull private final Collection mRelatedContent; private final int mInfoFlags; private final int mSignalQuality; @Nullable private final RadioMetadata mMetadata; @NonNull private final Map mVendorInfo; /** @hide */ public ProgramInfo(@NonNull ProgramSelector selector, @Nullable ProgramSelector.Identifier logicallyTunedTo, @Nullable ProgramSelector.Identifier physicallyTunedTo, @Nullable Collection relatedContent, int infoFlags, int signalQuality, @Nullable RadioMetadata metadata, @Nullable Map vendorInfo) { mSelector = Objects.requireNonNull(selector); mLogicallyTunedTo = logicallyTunedTo; mPhysicallyTunedTo = physicallyTunedTo; if (relatedContent == null) { mRelatedContent = Collections.emptyList(); } else { Preconditions.checkCollectionElementsNotNull(relatedContent, "relatedContent"); mRelatedContent = relatedContent; } mInfoFlags = infoFlags; mSignalQuality = signalQuality; 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; } /** * Identifier currently used for program selection. * * This identifier can be used to determine which technology is * currently being used for reception. * * Some program selectors contain tuning information for different radio * technologies (i.e. FM RDS and DAB). For example, user may tune using * a ProgramSelector with RDS_PI primary identifier, but the tuner hardware * may choose to use DAB technology to make actual tuning. This identifier * must reflect that. */ public @Nullable ProgramSelector.Identifier getLogicallyTunedTo() { return mLogicallyTunedTo; } /** * Identifier currently used by hardware to physically tune to a channel. * * Some radio technologies broadcast the same program on multiple channels, * i.e. with RDS AF the same program may be broadcasted on multiple * alternative frequencies; the same DAB program may be broadcast on * multiple ensembles. This identifier points to the channel to which the * radio hardware is physically tuned to. */ public @Nullable ProgramSelector.Identifier getPhysicallyTunedTo() { return mPhysicallyTunedTo; } /** * Primary identifiers of related contents. * * Some radio technologies provide pointers to other programs that carry * related content (i.e. DAB soft-links). This field is a list of pointers * to other programs on the program list. * * Please note, that these identifiers does not have to exist on the program * list - i.e. DAB tuner may provide information on FM RDS alternatives * despite not supporting FM RDS. If the system has multiple tuners, another * one may have it on its list. */ public @Nullable Collection getRelatedContent() { return mRelatedContent; } /** 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 (mInfoFlags & FLAG_TUNED) != 0; } /** {@code true} if the received program is stereo * @return {@code true} if stereo, {@code false} otherwise. */ public boolean isStereo() { return (mInfoFlags & FLAG_STEREO) != 0; } /** {@code true} if the received program is digital (e.g HD radio) * @return {@code true} if digital, {@code false} otherwise. * @deprecated Use {@link getLogicallyTunedTo()} instead. */ @Deprecated public boolean isDigital() { ProgramSelector.Identifier id = mLogicallyTunedTo; if (id == null) id = mSelector.getPrimaryId(); int type = id.getType(); return (type != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY && type != ProgramSelector.IDENTIFIER_TYPE_RDS_PI); } /** * {@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 (mInfoFlags & 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 (mInfoFlags & FLAG_MUTED) != 0; } /** * {@code true} if radio station transmits traffic information * regularily. */ public boolean isTrafficProgram() { return (mInfoFlags & FLAG_TRAFFIC_PROGRAM) != 0; } /** * {@code true} if radio station transmits traffic information * at the very moment. */ public boolean isTrafficAnnouncementActive() { return (mInfoFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0; } /** * Signal quality (as opposed to the name) indication from 0 (no signal) * to 100 (excellent) * @return the signal quality indication. */ public int getSignalStrength() { return mSignalQuality; } /** 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 getVendorInfo() { return mVendorInfo; } private ProgramInfo(Parcel in) { mSelector = Objects.requireNonNull(in.readTypedObject(ProgramSelector.CREATOR)); mLogicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR); mPhysicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR); mRelatedContent = in.createTypedArrayList(ProgramSelector.Identifier.CREATOR); mInfoFlags = in.readInt(); mSignalQuality = in.readInt(); mMetadata = in.readTypedObject(RadioMetadata.CREATOR); mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 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.writeTypedObject(mSelector, flags); dest.writeTypedObject(mLogicallyTunedTo, flags); dest.writeTypedObject(mPhysicallyTunedTo, flags); Utils.writeTypedCollection(dest, mRelatedContent); dest.writeInt(mInfoFlags); dest.writeInt(mSignalQuality); dest.writeTypedObject(mMetadata, flags); Utils.writeStringMap(dest, mVendorInfo); } @Override public int describeContents() { return 0; } @Override public String toString() { return "ProgramInfo" + " [selector=" + mSelector + ", logicallyTunedTo=" + Objects.toString(mLogicallyTunedTo) + ", physicallyTunedTo=" + Objects.toString(mPhysicallyTunedTo) + ", relatedContent=" + mRelatedContent.size() + ", infoFlags=" + mInfoFlags + ", mSignalQuality=" + mSignalQuality + ", mMetadata=" + Objects.toString(mMetadata) + "]"; } @Override public int hashCode() { return Objects.hash(mSelector, mLogicallyTunedTo, mPhysicallyTunedTo, mRelatedContent, mInfoFlags, mSignalQuality, mMetadata, mVendorInfo); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof ProgramInfo)) return false; ProgramInfo other = (ProgramInfo) obj; if (!Objects.equals(mSelector, other.mSelector)) return false; if (!Objects.equals(mLogicallyTunedTo, other.mLogicallyTunedTo)) return false; if (!Objects.equals(mPhysicallyTunedTo, other.mPhysicallyTunedTo)) return false; if (!Objects.equals(mRelatedContent, other.mRelatedContent)) return false; if (mInfoFlags != other.mInfoFlags) return false; if (mSignalQuality != other.mSignalQuality) return false; if (!Objects.equals(mMetadata, other.mMetadata)) return false; if (!Objects.equals(mVendorInfo, 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 *
    *
  • {@link #STATUS_OK} in case of success,
  • *
  • {@link #STATUS_ERROR} in case of unspecified error,
  • *
  • {@link #STATUS_NO_INIT} if the native service cannot be reached,
  • *
  • {@link #STATUS_BAD_VALUE} if modules is null,
  • *
  • {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails,
  • *
*/ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public int listModules(List 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 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 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 | IllegalArgumentException ex) { Log.e(TAG, "Failed to open tuner", ex); return null; } if (tuner == null) { Log.e(TAG, "Failed to open tuner"); return null; } return new TunerAdapter(tuner, halCallback, config != null ? config.getType() : BAND_INVALID); } private final Map mAnnouncementListeners = new HashMap<>(); /** * Adds new announcement listener. * * @param enabledAnnouncementTypes a set of announcement types to listen to * @param listener announcement listener */ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public void addAnnouncementListener(@NonNull Set enabledAnnouncementTypes, @NonNull Announcement.OnListUpdatedListener listener) { addAnnouncementListener(cmd -> cmd.run(), enabledAnnouncementTypes, listener); } /** * Adds new announcement listener with executor. * * @param executor the executor * @param enabledAnnouncementTypes a set of announcement types to listen to * @param listener announcement listener */ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public void addAnnouncementListener(@NonNull @CallbackExecutor Executor executor, @NonNull Set enabledAnnouncementTypes, @NonNull Announcement.OnListUpdatedListener listener) { Objects.requireNonNull(executor); Objects.requireNonNull(listener); int[] types = enabledAnnouncementTypes.stream().mapToInt(Integer::intValue).toArray(); IAnnouncementListener listenerIface = new IAnnouncementListener.Stub() { public void onListUpdated(List activeAnnouncements) { executor.execute(() -> listener.onListUpdated(activeAnnouncements)); } }; synchronized (mAnnouncementListeners) { ICloseHandle closeHandle = null; try { closeHandle = mService.addAnnouncementListener(types, listenerIface); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } Objects.requireNonNull(closeHandle); ICloseHandle oldCloseHandle = mAnnouncementListeners.put(listener, closeHandle); if (oldCloseHandle != null) Utils.close(oldCloseHandle); } } /** * Removes previously registered announcement listener. * * @param listener announcement listener, previously registered with * {@link addAnnouncementListener} */ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) { Objects.requireNonNull(listener); synchronized (mAnnouncementListeners) { ICloseHandle closeHandle = mAnnouncementListeners.remove(listener); if (closeHandle != null) Utils.close(closeHandle); } } @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)); } }