Implement front-end APIs for dynamic program list.
Bug: 69860743 Test: instrumentation Change-Id: I326865c690d315b867626599174e34911564ef9e
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package android.hardware.radio;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.hardware.radio.ProgramList;
|
||||
import android.hardware.radio.ProgramSelector;
|
||||
import android.hardware.radio.RadioManager;
|
||||
|
||||
@@ -73,14 +74,8 @@ interface ITuner {
|
||||
*/
|
||||
boolean startBackgroundScan();
|
||||
|
||||
/**
|
||||
* @param vendorFilter Vendor-specific filter, must be Map<String, String>
|
||||
* @return the list, or null if scan is in progress
|
||||
* @throws IllegalArgumentException if invalid arguments are passed
|
||||
* @throws IllegalStateException if the scan has not been started, client may
|
||||
* call startBackgroundScan to fix this.
|
||||
*/
|
||||
List<RadioManager.ProgramInfo> getProgramList(in Map vendorFilter);
|
||||
void startProgramListUpdates(in ProgramList.Filter filter);
|
||||
void stopProgramListUpdates();
|
||||
|
||||
boolean isConfigFlagSupported(int flag);
|
||||
boolean isConfigFlagSet(int flag);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.hardware.radio;
|
||||
|
||||
import android.hardware.radio.ProgramList;
|
||||
import android.hardware.radio.RadioManager;
|
||||
import android.hardware.radio.RadioMetadata;
|
||||
|
||||
@@ -30,6 +31,7 @@ oneway interface ITunerCallback {
|
||||
void onBackgroundScanAvailabilityChange(boolean isAvailable);
|
||||
void onBackgroundScanComplete();
|
||||
void onProgramListChanged();
|
||||
void onProgramListUpdated(in ProgramList.Chunk chunk);
|
||||
|
||||
/**
|
||||
* @param parameters Vendor-specific key-value pairs, must be Map<String, String>
|
||||
|
||||
23
core/java/android/hardware/radio/ProgramList.aidl
Normal file
23
core/java/android/hardware/radio/ProgramList.aidl
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/** @hide */
|
||||
parcelable ProgramList.Filter;
|
||||
|
||||
/** @hide */
|
||||
parcelable ProgramList.Chunk;
|
||||
427
core/java/android/hardware/radio/ProgramList.java
Normal file
427
core/java/android/hardware/radio/ProgramList.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
@SystemApi
|
||||
public final class ProgramSelector implements Parcelable {
|
||||
public static final int PROGRAM_TYPE_INVALID = 0;
|
||||
/** Analogue AM radio (with or without RDS). */
|
||||
public static final int PROGRAM_TYPE_AM = 1;
|
||||
/** analogue FM radio (with or without RDS). */
|
||||
@@ -77,6 +78,7 @@ public final class ProgramSelector implements Parcelable {
|
||||
public static final int PROGRAM_TYPE_VENDOR_START = 1000;
|
||||
public static final int PROGRAM_TYPE_VENDOR_END = 1999;
|
||||
@IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
|
||||
PROGRAM_TYPE_INVALID,
|
||||
PROGRAM_TYPE_AM,
|
||||
PROGRAM_TYPE_FM,
|
||||
PROGRAM_TYPE_AM_HD,
|
||||
@@ -89,6 +91,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 */
|
||||
@@ -148,6 +151,7 @@ public final class ProgramSelector implements Parcelable {
|
||||
public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START;
|
||||
public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END;
|
||||
@IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
|
||||
IDENTIFIER_TYPE_INVALID,
|
||||
IDENTIFIER_TYPE_AMFM_FREQUENCY,
|
||||
IDENTIFIER_TYPE_RDS_PI,
|
||||
IDENTIFIER_TYPE_HD_STATION_ID_EXT,
|
||||
@@ -268,7 +272,7 @@ 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.
|
||||
*/
|
||||
public @NonNull long[] getVendorIds() {
|
||||
return mVendorIds;
|
||||
|
||||
@@ -185,25 +185,6 @@ public class RadioManager {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ConfigFlag {}
|
||||
|
||||
private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) {
|
||||
dest.writeInt(map.size());
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
dest.writeString(entry.getKey());
|
||||
dest.writeString(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
|
||||
int size = in.readInt();
|
||||
Map<String, String> map = new HashMap<>();
|
||||
while (size-- > 0) {
|
||||
String key = in.readString();
|
||||
String value = in.readString();
|
||||
map.put(key, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Lists properties, options and radio bands supported by a given broadcast radio module.
|
||||
* Each module has a unique ID used to address it when calling RadioManager APIs.
|
||||
@@ -415,7 +396,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
|
||||
@@ -445,7 +426,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
|
||||
@@ -1410,7 +1391,7 @@ public class RadioManager {
|
||||
private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
|
||||
|
||||
@NonNull private final ProgramSelector mSelector;
|
||||
private final boolean mTuned;
|
||||
private final boolean mTuned; // TODO(b/69958777): replace with mFlags
|
||||
private final boolean mStereo;
|
||||
private final boolean mDigital;
|
||||
private final int mFlags;
|
||||
@@ -1418,7 +1399,8 @@ public class RadioManager {
|
||||
private final RadioMetadata mMetadata;
|
||||
@NonNull private final Map<String, String> mVendorInfo;
|
||||
|
||||
ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
|
||||
/** @hide */
|
||||
public ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
|
||||
boolean digital, int signalStrength, RadioMetadata metadata, int flags,
|
||||
Map<String, String> vendorInfo) {
|
||||
mSelector = selector;
|
||||
@@ -1564,7 +1546,7 @@ public class RadioManager {
|
||||
mMetadata = null;
|
||||
}
|
||||
mFlags = in.readInt();
|
||||
mVendorInfo = readStringMap(in);
|
||||
mVendorInfo = Utils.readStringMap(in);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ProgramInfo> CREATOR
|
||||
@@ -1592,7 +1574,7 @@ public class RadioManager {
|
||||
mMetadata.writeToParcel(dest, flags);
|
||||
}
|
||||
dest.writeInt(mFlags);
|
||||
writeStringMap(dest, mVendorInfo);
|
||||
Utils.writeStringMap(dest, mVendorInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1727,7 +1709,8 @@ public class RadioManager {
|
||||
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);
|
||||
}
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
|
||||
@@ -280,10 +280,28 @@ 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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
@@ -227,10 +234,55 @@ class TunerAdapter extends RadioTuner {
|
||||
@Override
|
||||
public @NonNull List<RadioManager.ProgramInfo>
|
||||
getProgramList(@Nullable Map<String, String> vendorFilter) {
|
||||
try {
|
||||
return mTuner.getProgramList(vendorFilter);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("service died", e);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -97,6 +160,14 @@ class TunerCallbackAdapter extends ITunerCallback.Stub {
|
||||
mHandler.post(() -> mCallback.onProgramListChanged());
|
||||
}
|
||||
|
||||
@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));
|
||||
|
||||
92
core/java/android/hardware/radio/Utils.java
Normal file
92
core/java/android/hardware/radio/Utils.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
final class 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];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user