/**
|
* 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() {
|
mIdentifierTypes = Collections.emptySet();
|
mIdentifiers = Collections.emptySet();
|
mIncludeCategories = false;
|
mExcludeModifications = false;
|
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 @android.annotation.NonNull 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 @android.annotation.NonNull 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;
|
}
|
}
|
}
|