| /** |
| * 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 <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 mIsInitializationRequired; |
| private final boolean mIsCaptureSupported; |
| private final BandDescriptor[] mBands; |
| private final boolean mIsBgScanSupported; |
| private final Set<Integer> mSupportedProgramTypes; |
| private final Set<Integer> mSupportedIdentifierTypes; |
| @Nullable private final Map<String, Integer> mDabFrequencyTable; |
| @NonNull private final Map<String, String> 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<String, Integer> dabFrequencyTable, |
| 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; |
| mIsInitializationRequired = isInitializationRequired; |
| mIsCaptureSupported = isCaptureSupported; |
| mBands = bands; |
| mIsBgScanSupported = isBgScanSupported; |
| mSupportedProgramTypes = arrayToSet(supportedProgramTypes); |
| mSupportedIdentifierTypes = arrayToSet(supportedIdentifierTypes); |
| if (dabFrequencyTable != null) { |
| for (Map.Entry<String, Integer> entry : dabFrequencyTable.entrySet()) { |
| Objects.requireNonNull(entry.getKey()); |
| Objects.requireNonNull(entry.getValue()); |
| } |
| } |
| mDabFrequencyTable = dabFrequencyTable; |
| 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; |
| } |
| |
| /** |
| * 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<String, Integer> 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<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(); |
| 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 @android.annotation.NonNull 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(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: |
| * <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 @android.annotation.NonNull 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; |
| |
| /** @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 @android.annotation.NonNull 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; |
| |
| /** @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 @android.annotation.NonNull 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 { |
| |
| @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: |
| * <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 @android.annotation.NonNull 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; |
| |
| /** @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 @android.annotation.NonNull 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; |
| |
| /** @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 @android.annotation.NonNull 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. */ |
| 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<ProgramSelector.Identifier> mRelatedContent; |
| private final int mInfoFlags; |
| private final int mSignalQuality; |
| @Nullable private final RadioMetadata mMetadata; |
| @NonNull private final Map<String, String> mVendorInfo; |
| |
| /** @hide */ |
| public ProgramInfo(@NonNull ProgramSelector selector, |
| @Nullable ProgramSelector.Identifier logicallyTunedTo, |
| @Nullable ProgramSelector.Identifier physicallyTunedTo, |
| @Nullable Collection<ProgramSelector.Identifier> relatedContent, |
| int infoFlags, int signalQuality, @Nullable RadioMetadata metadata, |
| @Nullable Map<String, String> 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<ProgramSelector.Identifier> 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<String, String> 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 @android.annotation.NonNull 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.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 |
| * <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 | 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<Announcement.OnListUpdatedListener, ICloseHandle> 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<Integer> 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<Integer> 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<Announcement> 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)); |
| } |
| } |