summaryrefslogtreecommitdiff
path: root/android/hardware/radio
diff options
context:
space:
mode:
authorJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
committerJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
commita192cc2a132cb0ee8588e2df755563ec7008c179 (patch)
tree380e4db22df19c819bd37df34bf06e7568916a50 /android/hardware/radio
parent98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff)
downloadandroid-28-a192cc2a132cb0ee8588e2df755563ec7008c179.tar.gz
Update fullsdk to 4575844
/google/data/ro/projects/android/fetch_artifact \ --bid 4575844 \ --target sdk_phone_x86_64-sdk \ sdk-repo-linux-sources-4575844.zip Test: TreeHugger Change-Id: I81e0eb157b4ac3b38408d0ef86f9d6286471f87a
Diffstat (limited to 'android/hardware/radio')
-rw-r--r--android/hardware/radio/Announcement.java133
-rw-r--r--android/hardware/radio/ProgramList.java427
-rw-r--r--android/hardware/radio/ProgramSelector.java164
-rw-r--r--android/hardware/radio/RadioManager.java401
-rw-r--r--android/hardware/radio/RadioTuner.java76
-rw-r--r--android/hardware/radio/TunerAdapter.java92
-rw-r--r--android/hardware/radio/TunerCallbackAdapter.java73
-rw-r--r--android/hardware/radio/Utils.java118
8 files changed, 1332 insertions, 152 deletions
diff --git a/android/hardware/radio/Announcement.java b/android/hardware/radio/Announcement.java
new file mode 100644
index 00000000..166fe604
--- /dev/null
+++ b/android/hardware/radio/Announcement.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class Announcement implements Parcelable {
+
+ /** DAB alarm, RDS emergency program type (PTY 31). */
+ public static final int TYPE_EMERGENCY = 1;
+ /** DAB warning. */
+ public static final int TYPE_WARNING = 2;
+ /** DAB road traffic, RDS TA, HD Radio transportation. */
+ public static final int TYPE_TRAFFIC = 3;
+ /** Weather. */
+ public static final int TYPE_WEATHER = 4;
+ /** News. */
+ public static final int TYPE_NEWS = 5;
+ /** DAB event, special event. */
+ public static final int TYPE_EVENT = 6;
+ /** DAB sport report, RDS sports. */
+ public static final int TYPE_SPORT = 7;
+ /** All others. */
+ public static final int TYPE_MISC = 8;
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_EMERGENCY,
+ TYPE_WARNING,
+ TYPE_TRAFFIC,
+ TYPE_WEATHER,
+ TYPE_NEWS,
+ TYPE_EVENT,
+ TYPE_SPORT,
+ TYPE_MISC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * Listener of announcement list events.
+ */
+ public interface OnListUpdatedListener {
+ /**
+ * An event called whenever a list of active announcements change.
+ *
+ * The entire list is sent each time a new announcement appears or any ends broadcasting.
+ *
+ * @param activeAnnouncements a full list of active announcements
+ */
+ void onListUpdated(Collection<Announcement> activeAnnouncements);
+ }
+
+ @NonNull private final ProgramSelector mSelector;
+ @Type private final int mType;
+ @NonNull private final Map<String, String> mVendorInfo;
+
+ /** @hide */
+ public Announcement(@NonNull ProgramSelector selector, @Type int type,
+ @NonNull Map<String, String> vendorInfo) {
+ mSelector = Objects.requireNonNull(selector);
+ mType = Objects.requireNonNull(type);
+ mVendorInfo = Objects.requireNonNull(vendorInfo);
+ }
+
+ private Announcement(@NonNull Parcel in) {
+ mSelector = in.readTypedObject(ProgramSelector.CREATOR);
+ mType = in.readInt();
+ mVendorInfo = Utils.readStringMap(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mSelector, 0);
+ dest.writeInt(mType);
+ Utils.writeStringMap(dest, mVendorInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Announcement> CREATOR =
+ new Parcelable.Creator<Announcement>() {
+ public Announcement createFromParcel(Parcel in) {
+ return new Announcement(in);
+ }
+
+ public Announcement[] newArray(int size) {
+ return new Announcement[size];
+ }
+ };
+
+ public @NonNull ProgramSelector getSelector() {
+ return mSelector;
+ }
+
+ public @Type int getType() {
+ return mType;
+ }
+
+ public @NonNull Map<String, String> getVendorInfo() {
+ return mVendorInfo;
+ }
+}
diff --git a/android/hardware/radio/ProgramList.java b/android/hardware/radio/ProgramList.java
new file mode 100644
index 00000000..b2aa9ba5
--- /dev/null
+++ b/android/hardware/radio/ProgramList.java
@@ -0,0 +1,427 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+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;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ProgramList implements AutoCloseable {
+
+ private final Object mLock = new Object();
+ private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
+ new HashMap<>();
+
+ private final List<ListCallback> mListCallbacks = new ArrayList<>();
+ private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+ private OnCloseListener mOnCloseListener;
+ private boolean mIsClosed = false;
+ private boolean mIsComplete = false;
+
+ ProgramList() {}
+
+ /**
+ * Callback for list change operations.
+ */
+ public abstract static class ListCallback {
+ /**
+ * Called when item was modified or added to the list.
+ */
+ public void onItemChanged(@NonNull ProgramSelector.Identifier id) { }
+
+ /**
+ * Called when item was removed from the list.
+ */
+ public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { }
+ }
+
+ /**
+ * Listener of list complete event.
+ */
+ public interface OnCompleteListener {
+ /**
+ * Called when the list turned complete (i.e. when the scan process
+ * came to an end).
+ */
+ void onComplete();
+ }
+
+ interface OnCloseListener {
+ void onClose();
+ }
+
+ /**
+ * Registers list change callback with executor.
+ */
+ public void registerListCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull ListCallback callback) {
+ registerListCallback(new ListCallback() {
+ public void onItemChanged(@NonNull ProgramSelector.Identifier id) {
+ executor.execute(() -> callback.onItemChanged(id));
+ }
+
+ public void onItemRemoved(@NonNull ProgramSelector.Identifier id) {
+ executor.execute(() -> callback.onItemRemoved(id));
+ }
+ });
+ }
+
+ /**
+ * Registers list change callback.
+ */
+ public void registerListCallback(@NonNull ListCallback callback) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mListCallbacks.add(Objects.requireNonNull(callback));
+ }
+ }
+
+ /**
+ * Unregisters list change callback.
+ */
+ public void unregisterListCallback(@NonNull ListCallback callback) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mListCallbacks.remove(Objects.requireNonNull(callback));
+ }
+ }
+
+ /**
+ * Adds list complete event listener with executor.
+ */
+ public void addOnCompleteListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OnCompleteListener listener) {
+ addOnCompleteListener(() -> executor.execute(listener::onComplete));
+ }
+
+ /**
+ * Adds list complete event listener.
+ */
+ public void addOnCompleteListener(@NonNull OnCompleteListener listener) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mOnCompleteListeners.add(Objects.requireNonNull(listener));
+ if (mIsComplete) listener.onComplete();
+ }
+ }
+
+ /**
+ * Removes list complete event listener.
+ */
+ public void removeOnCompleteListener(@NonNull OnCompleteListener listener) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mOnCompleteListeners.remove(Objects.requireNonNull(listener));
+ }
+ }
+
+ void setOnCloseListener(@Nullable OnCloseListener listener) {
+ synchronized (mLock) {
+ if (mOnCloseListener != null) {
+ throw new IllegalStateException("Close callback is already set");
+ }
+ mOnCloseListener = listener;
+ }
+ }
+
+ /**
+ * Disables list updates and releases all resources.
+ */
+ public void close() {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mIsClosed = true;
+ mPrograms.clear();
+ mListCallbacks.clear();
+ mOnCompleteListeners.clear();
+ if (mOnCloseListener != null) {
+ mOnCloseListener.onClose();
+ mOnCloseListener = null;
+ }
+ }
+ }
+
+ void apply(@NonNull Chunk chunk) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+
+ mIsComplete = false;
+
+ if (chunk.isPurge()) {
+ new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+ }
+
+ chunk.getRemoved().stream().forEach(id -> removeLocked(id));
+ chunk.getModified().stream().forEach(info -> putLocked(info));
+
+ if (chunk.isComplete()) {
+ mIsComplete = true;
+ mOnCompleteListeners.forEach(cb -> cb.onComplete());
+ }
+ }
+ }
+
+ private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+ ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
+ mPrograms.put(Objects.requireNonNull(key), value);
+ ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
+ mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+ }
+
+ private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+ RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
+ if (removed == null) return;
+ ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
+ mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+ }
+
+ /**
+ * Converts the program list in its current shape to the static List<>.
+ *
+ * @return the new List<> object; it won't receive any further updates
+ */
+ public @NonNull List<RadioManager.ProgramInfo> toList() {
+ synchronized (mLock) {
+ return mPrograms.values().stream().collect(Collectors.toList());
+ }
+ }
+
+ /**
+ * Returns the program with a specified primary identifier.
+ *
+ * @param id primary identifier of a program to fetch
+ * @return the program info, or null if there is no such program on the list
+ */
+ public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
+ synchronized (mLock) {
+ return mPrograms.get(Objects.requireNonNull(id));
+ }
+ }
+
+ /**
+ * Filter for the program list.
+ */
+ public static final class Filter implements Parcelable {
+ private final @NonNull Set<Integer> mIdentifierTypes;
+ private final @NonNull Set<ProgramSelector.Identifier> mIdentifiers;
+ private final boolean mIncludeCategories;
+ private final boolean mExcludeModifications;
+ private final @Nullable Map<String, String> mVendorFilter;
+
+ /**
+ * Constructor of program list filter.
+ *
+ * Arrays passed to this constructor become owned by this object, do not modify them later.
+ *
+ * @param identifierTypes see getIdentifierTypes()
+ * @param identifiers see getIdentifiers()
+ * @param includeCategories see areCategoriesIncluded()
+ * @param excludeModifications see areModificationsExcluded()
+ */
+ public Filter(@NonNull Set<Integer> identifierTypes,
+ @NonNull Set<ProgramSelector.Identifier> identifiers,
+ boolean includeCategories, boolean excludeModifications) {
+ mIdentifierTypes = Objects.requireNonNull(identifierTypes);
+ mIdentifiers = Objects.requireNonNull(identifiers);
+ mIncludeCategories = includeCategories;
+ mExcludeModifications = excludeModifications;
+ mVendorFilter = null;
+ }
+
+ /**
+ * @hide for framework use only
+ */
+ public Filter(@Nullable Map<String, String> vendorFilter) {
+ mIdentifierTypes = Collections.emptySet();
+ mIdentifiers = Collections.emptySet();
+ mIncludeCategories = false;
+ mExcludeModifications = false;
+ mVendorFilter = vendorFilter;
+ }
+
+ private Filter(@NonNull Parcel in) {
+ mIdentifierTypes = Utils.createIntSet(in);
+ mIdentifiers = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
+ mIncludeCategories = in.readByte() != 0;
+ mExcludeModifications = in.readByte() != 0;
+ mVendorFilter = Utils.readStringMap(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Utils.writeIntSet(dest, mIdentifierTypes);
+ Utils.writeSet(dest, mIdentifiers);
+ dest.writeByte((byte) (mIncludeCategories ? 1 : 0));
+ dest.writeByte((byte) (mExcludeModifications ? 1 : 0));
+ Utils.writeStringMap(dest, mVendorFilter);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() {
+ public Filter createFromParcel(Parcel in) {
+ return new Filter(in);
+ }
+
+ public Filter[] newArray(int size) {
+ return new Filter[size];
+ }
+ };
+
+ /**
+ * @hide for framework use only
+ */
+ public Map<String, String> getVendorFilter() {
+ return mVendorFilter;
+ }
+
+ /**
+ * Returns the list of identifier types that satisfy the filter.
+ *
+ * If the program list entry contains at least one identifier of the type
+ * listed, it satisfies this condition.
+ *
+ * Empty list means no filtering on identifier type.
+ *
+ * @return the list of accepted identifier types, must not be modified
+ */
+ public @NonNull Set<Integer> getIdentifierTypes() {
+ return mIdentifierTypes;
+ }
+
+ /**
+ * Returns the list of identifiers that satisfy the filter.
+ *
+ * If the program list entry contains at least one listed identifier,
+ * it satisfies this condition.
+ *
+ * Empty list means no filtering on identifier.
+ *
+ * @return the list of accepted identifiers, must not be modified
+ */
+ public @NonNull Set<ProgramSelector.Identifier> getIdentifiers() {
+ return mIdentifiers;
+ }
+
+ /**
+ * Checks, if non-tunable entries that define tree structure on the
+ * program list (i.e. DAB ensembles) should be included.
+ */
+ public boolean areCategoriesIncluded() {
+ return mIncludeCategories;
+ }
+
+ /**
+ * Checks, if updates on entry modifications should be disabled.
+ *
+ * If true, 'modified' vector of ProgramListChunk must contain list
+ * additions only. Once the program is added to the list, it's not
+ * updated anymore.
+ */
+ public boolean areModificationsExcluded() {
+ return mExcludeModifications;
+ }
+ }
+
+ /**
+ * @hide This is a transport class used for internal communication between
+ * Broadcast Radio Service and RadioManager.
+ * Do not use it directly.
+ */
+ public static final class Chunk implements Parcelable {
+ private final boolean mPurge;
+ private final boolean mComplete;
+ private final @NonNull Set<RadioManager.ProgramInfo> mModified;
+ private final @NonNull Set<ProgramSelector.Identifier> mRemoved;
+
+ public Chunk(boolean purge, boolean complete,
+ @Nullable Set<RadioManager.ProgramInfo> modified,
+ @Nullable Set<ProgramSelector.Identifier> removed) {
+ mPurge = purge;
+ mComplete = complete;
+ mModified = (modified != null) ? modified : Collections.emptySet();
+ mRemoved = (removed != null) ? removed : Collections.emptySet();
+ }
+
+ private Chunk(@NonNull Parcel in) {
+ mPurge = in.readByte() != 0;
+ mComplete = in.readByte() != 0;
+ mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR);
+ mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte((byte) (mPurge ? 1 : 0));
+ dest.writeByte((byte) (mComplete ? 1 : 0));
+ Utils.writeSet(dest, mModified);
+ Utils.writeSet(dest, mRemoved);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Chunk> CREATOR = new Parcelable.Creator<Chunk>() {
+ public Chunk createFromParcel(Parcel in) {
+ return new Chunk(in);
+ }
+
+ public Chunk[] newArray(int size) {
+ return new Chunk[size];
+ }
+ };
+
+ public boolean isPurge() {
+ return mPurge;
+ }
+
+ public boolean isComplete() {
+ return mComplete;
+ }
+
+ public @NonNull Set<RadioManager.ProgramInfo> getModified() {
+ return mModified;
+ }
+
+ public @NonNull Set<ProgramSelector.Identifier> getRemoved() {
+ return mRemoved;
+ }
+ }
+}
diff --git a/android/hardware/radio/ProgramSelector.java b/android/hardware/radio/ProgramSelector.java
index 2211cee9..0294a29b 100644
--- a/android/hardware/radio/ProgramSelector.java
+++ b/android/hardware/radio/ProgramSelector.java
@@ -27,6 +27,7 @@ import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@@ -59,24 +60,58 @@ import java.util.stream.Stream;
*/
@SystemApi
public final class ProgramSelector implements Parcelable {
- /** Analogue AM radio (with or without RDS). */
+ /** Invalid program type.
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
+ public static final int PROGRAM_TYPE_INVALID = 0;
+ /** Analogue AM radio (with or without RDS).
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_AM = 1;
- /** analogue FM radio (with or without RDS). */
+ /** analogue FM radio (with or without RDS).
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_FM = 2;
- /** AM HD Radio. */
+ /** AM HD Radio.
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_AM_HD = 3;
- /** FM HD Radio. */
+ /** FM HD Radio.
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_FM_HD = 4;
- /** Digital audio broadcasting. */
+ /** Digital audio broadcasting.
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_DAB = 5;
- /** Digital Radio Mondiale. */
+ /** Digital Radio Mondiale.
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_DRMO = 6;
- /** SiriusXM Satellite Radio. */
+ /** SiriusXM Satellite Radio.
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_SXM = 7;
- /** Vendor-specific, not synced across devices. */
+ /** Vendor-specific, not synced across devices.
+ * @deprecated use {@link ProgramIdentifier} instead
+ */
+ @Deprecated
public static final int PROGRAM_TYPE_VENDOR_START = 1000;
+ /** @deprecated use {@link ProgramIdentifier} instead */
+ @Deprecated
public static final int PROGRAM_TYPE_VENDOR_END = 1999;
+ /** @deprecated use {@link ProgramIdentifier} instead */
+ @Deprecated
@IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
+ PROGRAM_TYPE_INVALID,
PROGRAM_TYPE_AM,
PROGRAM_TYPE_FM,
PROGRAM_TYPE_AM_HD,
@@ -89,6 +124,7 @@ public final class ProgramSelector implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface ProgramType {}
+ public static final int IDENTIFIER_TYPE_INVALID = 0;
/** kHz */
public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
/** 16bit */
@@ -109,18 +145,46 @@ public final class ProgramSelector implements Parcelable {
*
* The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
* as opposed to HD Radio standard (where it's 1-based).
+ *
+ * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4;
/**
- * 24bit compound primary identifier for DAB.
+ * 64bit additional identifier for HD Radio.
+ *
+ * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
+ * globally unique. To provide a best-effort solution, a short version of
+ * station name may be carried as additional identifier and may be used
+ * by the tuner hardware to double-check tuning.
+ *
+ * The name is limited to the first 8 A-Z0-9 characters (lowercase letters
+ * must be converted to uppercase). Encoded in little-endian ASCII:
+ * the first character of the name is the LSB.
+ *
+ * For example: "Abc" is encoded as 0x434241.
+ */
+ public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
+ /**
+ * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+ */
+ public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
+ /**
+ * 28bit compound primary identifier for Digital Audio Broadcasting.
*
* Consists of (from the LSB):
* - 16bit: SId;
- * - 8bit: ECC code.
+ * - 8bit: ECC code;
+ * - 4bit: SCIdS.
+ *
+ * SCIdS (Service Component Identifier within the Service) value
+ * of 0 represents the main service, while 1 and above represents
+ * secondary services.
+ *
* The remaining bits should be set to zeros when writing on the chip side
* and ignored when read.
*/
- public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
+ public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC;
/** 16bit */
public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
/** 12bit */
@@ -131,7 +195,11 @@ public final class ProgramSelector implements Parcelable {
public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
/** kHz */
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
- /** 1: AM, 2:FM */
+ /**
+ * 1: AM, 2:FM
+ * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
+ */
+ @Deprecated
public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
/** 32bit */
public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
@@ -145,13 +213,29 @@ public final class ProgramSelector implements Parcelable {
* type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
* not be used in any program type other than 1015).
*/
- public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START;
- public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END;
+ public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
+ /**
+ * @see {@link IDENTIFIER_TYPE_VENDOR_START}
+ */
+ public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
+ /**
+ * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead
+ */
+ @Deprecated
+ public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START;
+ /**
+ * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead
+ */
+ @Deprecated
+ public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
@IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
+ IDENTIFIER_TYPE_INVALID,
IDENTIFIER_TYPE_AMFM_FREQUENCY,
IDENTIFIER_TYPE_RDS_PI,
IDENTIFIER_TYPE_HD_STATION_ID_EXT,
IDENTIFIER_TYPE_HD_SUBCHANNEL,
+ IDENTIFIER_TYPE_HD_STATION_NAME,
+ IDENTIFIER_TYPE_DAB_SID_EXT,
IDENTIFIER_TYPE_DAB_SIDECC,
IDENTIFIER_TYPE_DAB_ENSEMBLE,
IDENTIFIER_TYPE_DAB_SCID,
@@ -162,7 +246,7 @@ public final class ProgramSelector implements Parcelable {
IDENTIFIER_TYPE_SXM_SERVICE_ID,
IDENTIFIER_TYPE_SXM_CHANNEL,
})
- @IntRange(from = IDENTIFIER_TYPE_VENDOR_PRIMARY_START, to = IDENTIFIER_TYPE_VENDOR_PRIMARY_END)
+ @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
@Retention(RetentionPolicy.SOURCE)
public @interface IdentifierType {}
@@ -201,7 +285,9 @@ public final class ProgramSelector implements Parcelable {
* Type of a radio technology.
*
* @return program type.
+ * @deprecated use {@link getPrimaryId} instead
*/
+ @Deprecated
public @ProgramType int getProgramType() {
return mProgramType;
}
@@ -268,13 +354,48 @@ public final class ProgramSelector implements Parcelable {
* Vendor identifiers are passed as-is to the HAL implementation,
* preserving elements order.
*
- * @return a array of vendor identifiers, must not be modified.
+ * @return an array of vendor identifiers, must not be modified.
+ * @deprecated for HAL 1.x compatibility;
+ * HAL 2.x uses standard primary/secondary lists for vendor IDs
*/
+ @Deprecated
public @NonNull long[] getVendorIds() {
return mVendorIds;
}
/**
+ * Creates an equivalent ProgramSelector with a given secondary identifier preferred.
+ *
+ * Used to point to a specific physical identifier for technologies that may broadcast the same
+ * program on different channels. For example, with a DAB program broadcasted over multiple
+ * ensembles, the radio hardware may select the one with the strongest signal. The UI may select
+ * preferred ensemble though, so the radio hardware may try to use it in the first place.
+ *
+ * This is a best-effort hint for the tuner, not a guaranteed behavior.
+ *
+ * Setting the given secondary identifier as preferred means filtering out other secondary
+ * identifiers of its type and adding it to the list.
+ *
+ * @param preferred preferred secondary identifier
+ * @return a new ProgramSelector with a given secondary identifier preferred
+ */
+ public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) {
+ int preferredType = preferred.getType();
+ Identifier[] secondaryIds = Stream.concat(
+ // remove other identifiers of that type
+ Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType),
+ // add preferred identifier instead
+ Stream.of(preferred)).toArray(Identifier[]::new);
+
+ return new ProgramSelector(
+ mProgramType,
+ mPrimaryId,
+ secondaryIds,
+ mVendorIds
+ );
+ }
+
+ /**
* Builds new ProgramSelector for AM/FM frequency.
*
* @param band the band.
@@ -423,6 +544,10 @@ public final class ProgramSelector implements Parcelable {
private final long mValue;
public Identifier(@IdentifierType int type, long value) {
+ if (type == IDENTIFIER_TYPE_HD_STATION_NAME) {
+ // see getType
+ type = IDENTIFIER_TYPE_HD_SUBCHANNEL;
+ }
mType = type;
mValue = value;
}
@@ -433,6 +558,13 @@ public final class ProgramSelector implements Parcelable {
* @return type of an identifier.
*/
public @IdentifierType int getType() {
+ if (mType == IDENTIFIER_TYPE_HD_SUBCHANNEL && mValue > 10) {
+ /* HD_SUBCHANNEL and HD_STATION_NAME use the same identifier type, but they differ
+ * in possible values: sub channel is 0-7, station name is greater than ASCII space
+ * code (32).
+ */
+ return IDENTIFIER_TYPE_HD_STATION_NAME;
+ }
return mType;
}
diff --git a/android/hardware/radio/RadioManager.java b/android/hardware/radio/RadioManager.java
index 4d54e31b..b00f6033 100644
--- a/android/hardware/radio/RadioManager.java
+++ b/android/hardware/radio/RadioManager.java
@@ -17,8 +17,10 @@
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.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -32,13 +34,19 @@ 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;
/**
@@ -119,24 +127,70 @@ public class RadioManager {
* @see BandDescriptor */
public static final int REGION_KOREA = 4;
- private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) {
- dest.writeInt(map.size());
- for (Map.Entry<String, String> entry : map.entrySet()) {
- dest.writeString(entry.getKey());
- dest.writeString(entry.getValue());
- }
- }
-
- private static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
- int size = in.readInt();
- Map<String, String> map = new HashMap<>();
- while (size-- > 0) {
- String key = in.readString();
- String value = in.readString();
- map.put(key, value);
- }
- return map;
- }
+ /**
+ * 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.
@@ -349,7 +403,7 @@ public class RadioManager {
mIsBgScanSupported = in.readInt() == 1;
mSupportedProgramTypes = arrayToSet(in.createIntArray());
mSupportedIdentifierTypes = arrayToSet(in.createIntArray());
- mVendorInfo = readStringMap(in);
+ mVendorInfo = Utils.readStringMap(in);
}
public static final Parcelable.Creator<ModuleProperties> CREATOR
@@ -379,7 +433,7 @@ public class RadioManager {
dest.writeInt(mIsBgScanSupported ? 1 : 0);
dest.writeIntArray(setToArray(mSupportedProgramTypes));
dest.writeIntArray(setToArray(mSupportedIdentifierTypes));
- writeStringMap(dest, mVendorInfo);
+ Utils.writeStringMap(dest, mVendorInfo);
}
@Override
@@ -645,7 +699,8 @@ public class RadioManager {
private final boolean mAf;
private final boolean mEa;
- FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ /** @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;
@@ -771,7 +826,8 @@ public class RadioManager {
private final boolean mStereo;
- AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ /** @hide */
+ public AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
boolean stereo) {
super(region, type, lowerLimit, upperLimit, spacing);
mStereo = stereo;
@@ -843,10 +899,10 @@ public class RadioManager {
/** Radio band configuration. */
public static class BandConfig implements Parcelable {
- final BandDescriptor mDescriptor;
+ @NonNull final BandDescriptor mDescriptor;
BandConfig(BandDescriptor descriptor) {
- mDescriptor = descriptor;
+ mDescriptor = Objects.requireNonNull(descriptor);
}
BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) {
@@ -968,7 +1024,8 @@ public class RadioManager {
private final boolean mAf;
private final boolean mEa;
- FmBandConfig(FmBandDescriptor descriptor) {
+ /** @hide */
+ public FmBandConfig(FmBandDescriptor descriptor) {
super((BandDescriptor)descriptor);
mStereo = descriptor.isStereoSupported();
mRds = descriptor.isRdsSupported();
@@ -1204,7 +1261,8 @@ public class RadioManager {
public static class AmBandConfig extends BandConfig {
private final boolean mStereo;
- AmBandConfig(AmBandDescriptor descriptor) {
+ /** @hide */
+ public AmBandConfig(AmBandDescriptor descriptor) {
super((BandDescriptor)descriptor);
mStereo = descriptor.isStereoSupported();
}
@@ -1329,34 +1387,44 @@ public class RadioManager {
};
}
- /** Radio program information returned by
- * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */
+ /** Radio program information. */
public static class ProgramInfo implements Parcelable {
- // sourced from hardware/interfaces/broadcastradio/1.1/types.hal
+ // 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;
- private final boolean mTuned;
- private final boolean mStereo;
- private final boolean mDigital;
- private final int mFlags;
- private final int mSignalStrength;
- private final RadioMetadata mMetadata;
+ @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;
- ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
- boolean digital, int signalStrength, RadioMetadata metadata, int flags,
- Map<String, String> vendorInfo) {
- mSelector = selector;
- mTuned = tuned;
- mStereo = stereo;
- mDigital = digital;
- mFlags = flags;
- mSignalStrength = signalStrength;
+ /** @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;
}
@@ -1370,6 +1438,51 @@ public class RadioManager {
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
@@ -1404,19 +1517,28 @@ public class RadioManager {
* @return {@code true} if currently tuned, {@code false} otherwise.
*/
public boolean isTuned() {
- return mTuned;
+ 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 mStereo;
+ 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() {
- return mDigital;
+ 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);
}
/**
@@ -1425,7 +1547,7 @@ public class RadioManager {
* usually targetted at reduced latency.
*/
public boolean isLive() {
- return (mFlags & FLAG_LIVE) != 0;
+ return (mInfoFlags & FLAG_LIVE) != 0;
}
/**
@@ -1435,7 +1557,7 @@ public class RadioManager {
* It does NOT mean the user has muted audio.
*/
public boolean isMuted() {
- return (mFlags & FLAG_MUTED) != 0;
+ return (mInfoFlags & FLAG_MUTED) != 0;
}
/**
@@ -1443,7 +1565,7 @@ public class RadioManager {
* regularily.
*/
public boolean isTrafficProgram() {
- return (mFlags & FLAG_TRAFFIC_PROGRAM) != 0;
+ return (mInfoFlags & FLAG_TRAFFIC_PROGRAM) != 0;
}
/**
@@ -1451,15 +1573,18 @@ public class RadioManager {
* at the very moment.
*/
public boolean isTrafficAnnouncementActive() {
- return (mFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0;
+ return (mInfoFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0;
}
- /** Signal strength indicator from 0 (no signal) to 100 (excellent)
- * @return the signal strength indication.
+ /**
+ * Signal quality (as opposed to the name) indication from 0 (no signal)
+ * to 100 (excellent)
+ * @return the signal quality indication.
*/
public int getSignalStrength() {
- return mSignalStrength;
+ return mSignalQuality;
}
+
/** Metadata currently received from this station.
* null if no metadata have been received
* @return current meta data received from this program.
@@ -1483,18 +1608,14 @@ public class RadioManager {
}
private ProgramInfo(Parcel in) {
- mSelector = in.readParcelable(null);
- mTuned = in.readByte() == 1;
- mStereo = in.readByte() == 1;
- mDigital = in.readByte() == 1;
- mSignalStrength = in.readInt();
- if (in.readByte() == 1) {
- mMetadata = RadioMetadata.CREATOR.createFromParcel(in);
- } else {
- mMetadata = null;
- }
- mFlags = in.readInt();
- mVendorInfo = readStringMap(in);
+ 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<ProgramInfo> CREATOR
@@ -1510,19 +1631,14 @@ public class RadioManager {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mSelector, 0);
- dest.writeByte((byte)(mTuned ? 1 : 0));
- dest.writeByte((byte)(mStereo ? 1 : 0));
- dest.writeByte((byte)(mDigital ? 1 : 0));
- dest.writeInt(mSignalStrength);
- if (mMetadata == null) {
- dest.writeByte((byte)0);
- } else {
- dest.writeByte((byte)1);
- mMetadata.writeToParcel(dest, flags);
- }
- dest.writeInt(mFlags);
- writeStringMap(dest, mVendorInfo);
+ 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
@@ -1532,52 +1648,38 @@ public class RadioManager {
@Override
public String toString() {
- return "ProgramInfo [mSelector=" + mSelector
- + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
- + ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength
- + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.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() {
- final int prime = 31;
- int result = 1;
- result = prime * result + mSelector.hashCode();
- result = prime * result + (mTuned ? 1 : 0);
- result = prime * result + (mStereo ? 1 : 0);
- result = prime * result + (mDigital ? 1 : 0);
- result = prime * result + mFlags;
- result = prime * result + mSignalStrength;
- result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode());
- result = prime * result + mVendorInfo.hashCode();
- return result;
+ 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;
+ if (this == obj) return true;
+ if (!(obj instanceof ProgramInfo)) return false;
ProgramInfo other = (ProgramInfo) obj;
- if (!mSelector.equals(other.getSelector())) return false;
- if (mTuned != other.isTuned())
- return false;
- if (mStereo != other.isStereo())
- return false;
- if (mDigital != other.isDigital())
- return false;
- if (mFlags != other.mFlags)
- return false;
- if (mSignalStrength != other.getSignalStrength())
- return false;
- if (mMetadata == null) {
- if (other.getMetadata() != null)
- return false;
- } else if (!mMetadata.equals(other.getMetadata()))
- return false;
- if (!mVendorInfo.equals(other.mVendorInfo)) return false;
+
+ 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;
}
}
@@ -1649,15 +1751,78 @@ public class RadioManager {
TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
try {
tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to open tuner", e);
+ } 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, config != null ? config.getType() : BAND_INVALID);
+ 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;
diff --git a/android/hardware/radio/RadioTuner.java b/android/hardware/radio/RadioTuner.java
index e93fd5f1..ed20c4aa 100644
--- a/android/hardware/radio/RadioTuner.java
+++ b/android/hardware/radio/RadioTuner.java
@@ -280,17 +280,37 @@ public abstract class RadioTuner {
* @throws IllegalStateException if the scan is in progress or has not been started,
* startBackgroundScan() call may fix it.
* @throws IllegalArgumentException if the vendorFilter argument is not valid.
+ * @deprecated Use {@link getDynamicProgramList} instead.
*/
+ @Deprecated
public abstract @NonNull List<RadioManager.ProgramInfo>
getProgramList(@Nullable Map<String, String> vendorFilter);
/**
+ * Get the dynamic list of discovered radio stations.
+ *
+ * The list object is updated asynchronously; to get the updates register
+ * with {@link ProgramList#addListCallback}.
+ *
+ * When the returned object is no longer used, it must be closed.
+ *
+ * @param filter filter for the list, or null to get the full list.
+ * @return the dynamic program list object, close it after use
+ * or {@code null} if program list is not supported by the tuner
+ */
+ public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+ return null;
+ }
+
+ /**
* Checks, if the analog playback is forced, see setAnalogForced.
*
* @throws IllegalStateException if the switch is not supported at current
* configuration.
* @return {@code true} if analog is forced, {@code false} otherwise.
+ * @deprecated Use {@link isConfigFlagSet(int)} instead.
*/
+ @Deprecated
public abstract boolean isAnalogForced();
/**
@@ -305,10 +325,50 @@ public abstract class RadioTuner {
* @param isForced {@code true} to force analog, {@code false} for a default behaviour.
* @throws IllegalStateException if the switch is not supported at current
* configuration.
+ * @deprecated Use {@link setConfigFlag(int, boolean)} instead.
*/
+ @Deprecated
public abstract void setAnalogForced(boolean isForced);
/**
+ * Checks, if a given config flag is supported
+ *
+ * @param flag Flag to check.
+ * @return True, if the flag is supported.
+ */
+ public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
+ return false;
+ }
+
+ /**
+ * Fetches the current setting of a given config flag.
+ *
+ * The success/failure result is consistent with isConfigFlagSupported.
+ *
+ * @param flag Flag to fetch.
+ * @return The current value of the flag.
+ * @throws IllegalStateException if the flag is not applicable right now.
+ * @throws UnsupportedOperationException if the flag is not supported at all.
+ */
+ public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the config flag.
+ *
+ * The success/failure result is consistent with isConfigFlagSupported.
+ *
+ * @param flag Flag to set.
+ * @param value The new value of a given flag.
+ * @throws IllegalStateException if the flag is not applicable right now.
+ * @throws UnsupportedOperationException if the flag is not supported at all.
+ */
+ public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Generic method for setting vendor-specific parameter values.
* The framework does not interpret the parameters, they are passed
* in an opaque manner between a vendor application and HAL.
@@ -316,6 +376,7 @@ public abstract class RadioTuner {
* Framework does not make any assumptions on the keys or values, other than
* ones stated in VendorKeyValue documentation (a requirement of key
* prefixes).
+ * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal.
*
* For each pair in the result map, the key will be one of the keys
* contained in the input (possibly with wildcards expanded), and the value
@@ -332,10 +393,11 @@ public abstract class RadioTuner {
*
* @param parameters Vendor-specific key-value pairs.
* @return Operation completion status for parameters being set.
- * @hide FutureFeature
*/
- public abstract @NonNull Map<String, String>
- setParameters(@NonNull Map<String, String> parameters);
+ public @NonNull Map<String, String>
+ setParameters(@NonNull Map<String, String> parameters) {
+ throw new UnsupportedOperationException();
+ }
/**
* Generic method for retrieving vendor-specific parameter values.
@@ -355,10 +417,11 @@ public abstract class RadioTuner {
*
* @param keys Parameter keys to fetch.
* @return Vendor-specific key-value pairs.
- * @hide FutureFeature
*/
- public abstract @NonNull Map<String, String>
- getParameters(@NonNull List<String> keys);
+ public @NonNull Map<String, String>
+ getParameters(@NonNull List<String> keys) {
+ throw new UnsupportedOperationException();
+ }
/**
* Get current antenna connection state for current configuration.
@@ -494,7 +557,6 @@ public abstract class RadioTuner {
* asynchronously.
*
* @param parameters Vendor-specific key-value pairs.
- * @hide FutureFeature
*/
public void onParametersUpdated(@NonNull Map<String, String> parameters) {}
}
diff --git a/android/hardware/radio/TunerAdapter.java b/android/hardware/radio/TunerAdapter.java
index 864d17c2..91944bfd 100644
--- a/android/hardware/radio/TunerAdapter.java
+++ b/android/hardware/radio/TunerAdapter.java
@@ -33,15 +33,18 @@ class TunerAdapter extends RadioTuner {
private static final String TAG = "BroadcastRadio.TunerAdapter";
@NonNull private final ITuner mTuner;
+ @NonNull private final TunerCallbackAdapter mCallback;
private boolean mIsClosed = false;
private @RadioManager.Band int mBand;
- TunerAdapter(ITuner tuner, @RadioManager.Band int band) {
- if (tuner == null) {
- throw new NullPointerException();
- }
- mTuner = tuner;
+ private ProgramList mLegacyListProxy;
+ private Map<String, String> mLegacyListFilter;
+
+ TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback,
+ @RadioManager.Band int band) {
+ mTuner = Objects.requireNonNull(tuner);
+ mCallback = Objects.requireNonNull(callback);
mBand = band;
}
@@ -53,6 +56,10 @@ class TunerAdapter extends RadioTuner {
return;
}
mIsClosed = true;
+ if (mLegacyListProxy != null) {
+ mLegacyListProxy.close();
+ mLegacyListProxy = null;
+ }
}
try {
mTuner.close();
@@ -63,6 +70,7 @@ class TunerAdapter extends RadioTuner {
@Override
public int setConfiguration(RadioManager.BandConfig config) {
+ if (config == null) return RadioManager.STATUS_BAD_VALUE;
try {
mTuner.setConfiguration(config);
mBand = config.getType();
@@ -226,26 +234,90 @@ class TunerAdapter extends RadioTuner {
@Override
public @NonNull List<RadioManager.ProgramInfo>
getProgramList(@Nullable Map<String, String> vendorFilter) {
+ synchronized (mTuner) {
+ if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) {
+ Log.i(TAG, "Program list filter has changed, requesting new list");
+ mLegacyListProxy = new ProgramList();
+ mLegacyListFilter = vendorFilter;
+
+ mCallback.clearLastCompleteList();
+ mCallback.setProgramListObserver(mLegacyListProxy, () -> { });
+ try {
+ mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
+ } catch (RemoteException ex) {
+ throw new RuntimeException("service died", ex);
+ }
+ }
+
+ List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
+ if (list == null) throw new IllegalStateException("Program list is not ready yet");
+ return list;
+ }
+ }
+
+ @Override
+ public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+ synchronized (mTuner) {
+ if (mLegacyListProxy != null) {
+ mLegacyListProxy.close();
+ mLegacyListProxy = null;
+ }
+ mLegacyListFilter = null;
+
+ ProgramList list = new ProgramList();
+ mCallback.setProgramListObserver(list, () -> {
+ try {
+ mTuner.stopProgramListUpdates();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't stop program list updates", ex);
+ }
+ });
+
+ try {
+ mTuner.startProgramListUpdates(filter);
+ } catch (UnsupportedOperationException ex) {
+ return null;
+ } catch (RemoteException ex) {
+ mCallback.setProgramListObserver(null, () -> { });
+ throw new RuntimeException("service died", ex);
+ }
+
+ return list;
+ }
+ }
+
+ @Override
+ public boolean isAnalogForced() {
+ return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG);
+ }
+
+ @Override
+ public void setAnalogForced(boolean isForced) {
+ setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced);
+ }
+
+ @Override
+ public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
try {
- return mTuner.getProgramList(vendorFilter);
+ return mTuner.isConfigFlagSupported(flag);
} catch (RemoteException e) {
throw new RuntimeException("service died", e);
}
}
@Override
- public boolean isAnalogForced() {
+ public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
try {
- return mTuner.isAnalogForced();
+ return mTuner.isConfigFlagSet(flag);
} catch (RemoteException e) {
throw new RuntimeException("service died", e);
}
}
@Override
- public void setAnalogForced(boolean isForced) {
+ public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
try {
- mTuner.setAnalogForced(isForced);
+ mTuner.setConfigFlag(flag, value);
} catch (RemoteException e) {
throw new RuntimeException("service died", e);
}
diff --git a/android/hardware/radio/TunerCallbackAdapter.java b/android/hardware/radio/TunerCallbackAdapter.java
index a01f658e..b299ffe0 100644
--- a/android/hardware/radio/TunerCallbackAdapter.java
+++ b/android/hardware/radio/TunerCallbackAdapter.java
@@ -22,7 +22,9 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
@@ -30,9 +32,14 @@ import java.util.Map;
class TunerCallbackAdapter extends ITunerCallback.Stub {
private static final String TAG = "BroadcastRadio.TunerCallbackAdapter";
+ private final Object mLock = new Object();
@NonNull private final RadioTuner.Callback mCallback;
@NonNull private final Handler mHandler;
+ @Nullable ProgramList mProgramList;
+ @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; // for legacy getProgramList call
+ private boolean mDelayedCompleteCallback = false;
+
TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
mCallback = callback;
if (handler == null) {
@@ -42,6 +49,49 @@ class TunerCallbackAdapter extends ITunerCallback.Stub {
}
}
+ void setProgramListObserver(@Nullable ProgramList programList,
+ @NonNull ProgramList.OnCloseListener closeListener) {
+ Objects.requireNonNull(closeListener);
+ synchronized (mLock) {
+ if (mProgramList != null) {
+ Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
+ mProgramList.close();
+ }
+ mProgramList = programList;
+ if (programList == null) return;
+ programList.setOnCloseListener(() -> {
+ synchronized (mLock) {
+ if (mProgramList != programList) return;
+ mProgramList = null;
+ mLastCompleteList = null;
+ closeListener.onClose();
+ }
+ });
+ programList.addOnCompleteListener(() -> {
+ synchronized (mLock) {
+ if (mProgramList != programList) return;
+ mLastCompleteList = programList.toList();
+ if (mDelayedCompleteCallback) {
+ Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
+ sendBackgroundScanCompleteLocked();
+ }
+ }
+ });
+ }
+ }
+
+ @Nullable List<RadioManager.ProgramInfo> getLastCompleteList() {
+ synchronized (mLock) {
+ return mLastCompleteList;
+ }
+ }
+
+ void clearLastCompleteList() {
+ synchronized (mLock) {
+ mLastCompleteList = null;
+ }
+ }
+
@Override
public void onError(int status) {
mHandler.post(() -> mCallback.onError(status));
@@ -87,9 +137,22 @@ class TunerCallbackAdapter extends ITunerCallback.Stub {
mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable));
}
+ private void sendBackgroundScanCompleteLocked() {
+ mDelayedCompleteCallback = false;
+ mHandler.post(() -> mCallback.onBackgroundScanComplete());
+ }
+
@Override
public void onBackgroundScanComplete() {
- mHandler.post(() -> mCallback.onBackgroundScanComplete());
+ synchronized (mLock) {
+ if (mLastCompleteList == null) {
+ Log.i(TAG, "Got onBackgroundScanComplete callback, but the "
+ + "program list didn't get through yet. Delaying it...");
+ mDelayedCompleteCallback = true;
+ return;
+ }
+ sendBackgroundScanCompleteLocked();
+ }
}
@Override
@@ -98,6 +161,14 @@ class TunerCallbackAdapter extends ITunerCallback.Stub {
}
@Override
+ public void onProgramListUpdated(ProgramList.Chunk chunk) {
+ synchronized (mLock) {
+ if (mProgramList == null) return;
+ mProgramList.apply(Objects.requireNonNull(chunk));
+ }
+ }
+
+ @Override
public void onParametersUpdated(Map parameters) {
mHandler.post(() -> mCallback.onParametersUpdated(parameters));
}
diff --git a/android/hardware/radio/Utils.java b/android/hardware/radio/Utils.java
new file mode 100644
index 00000000..f1b58974
--- /dev/null
+++ b/android/hardware/radio/Utils.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+final class Utils {
+ private static final String TAG = "BroadcastRadio.utils";
+
+ static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) {
+ if (map == null) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(map.size());
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+ }
+
+ static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
+ int size = in.readInt();
+ Map<String, String> map = new HashMap<>();
+ while (size-- > 0) {
+ String key = in.readString();
+ String value = in.readString();
+ map.put(key, value);
+ }
+ return map;
+ }
+
+ static <T extends Parcelable> void writeSet(@NonNull Parcel dest, @Nullable Set<T> set) {
+ if (set == null) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(set.size());
+ set.stream().forEach(elem -> dest.writeTypedObject(elem, 0));
+ }
+
+ static <T> Set<T> createSet(@NonNull Parcel in, Parcelable.Creator<T> c) {
+ int size = in.readInt();
+ Set<T> set = new HashSet<>();
+ while (size-- > 0) {
+ set.add(in.readTypedObject(c));
+ }
+ return set;
+ }
+
+ static void writeIntSet(@NonNull Parcel dest, @Nullable Set<Integer> set) {
+ if (set == null) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(set.size());
+ set.stream().forEach(elem -> dest.writeInt(Objects.requireNonNull(elem)));
+ }
+
+ static Set<Integer> createIntSet(@NonNull Parcel in) {
+ return createSet(in, new Parcelable.Creator<Integer>() {
+ public Integer createFromParcel(Parcel in) {
+ return in.readInt();
+ }
+
+ public Integer[] newArray(int size) {
+ return new Integer[size];
+ }
+ });
+ }
+
+ static <T extends Parcelable> void writeTypedCollection(@NonNull Parcel dest,
+ @Nullable Collection<T> coll) {
+ ArrayList<T> list = null;
+ if (coll != null) {
+ if (coll instanceof ArrayList) {
+ list = (ArrayList) coll;
+ } else {
+ list = new ArrayList<>(coll);
+ }
+ }
+ dest.writeTypedList(list);
+ }
+
+ static void close(ICloseHandle handle) {
+ try {
+ handle.close();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+}