Loading api/system-current.txt +37 −1 Original line number Diff line number Diff line Loading @@ -1733,6 +1733,39 @@ package android.hardware.location { package android.hardware.radio { public final class ProgramList implements java.lang.AutoCloseable { method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener); method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); method public void close(); method public android.hardware.radio.RadioManager.ProgramInfo get(android.hardware.radio.ProgramSelector.Identifier); method public void registerListCallback(java.util.concurrent.Executor, android.hardware.radio.ProgramList.ListCallback); method public void registerListCallback(android.hardware.radio.ProgramList.ListCallback); method public void removeOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); method public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> toList(); method public void unregisterListCallback(android.hardware.radio.ProgramList.ListCallback); } public static final class ProgramList.Filter implements android.os.Parcelable { ctor public ProgramList.Filter(java.util.Set<java.lang.Integer>, java.util.Set<android.hardware.radio.ProgramSelector.Identifier>, boolean, boolean); method public boolean areCategoriesIncluded(); method public boolean areModificationsExcluded(); method public int describeContents(); method public java.util.Set<java.lang.Integer> getIdentifierTypes(); method public java.util.Set<android.hardware.radio.ProgramSelector.Identifier> getIdentifiers(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramList.Filter> CREATOR; } public static abstract class ProgramList.ListCallback { ctor public ProgramList.ListCallback(); method public void onItemChanged(android.hardware.radio.ProgramSelector.Identifier); method public void onItemRemoved(android.hardware.radio.ProgramSelector.Identifier); } public static abstract interface ProgramList.OnCompleteListener { method public abstract void onComplete(); } public final class ProgramSelector implements android.os.Parcelable { ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]); method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int); Loading @@ -1756,6 +1789,7 @@ package android.hardware.radio { field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc Loading @@ -1767,6 +1801,7 @@ package android.hardware.radio { field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6 field public static final int PROGRAM_TYPE_FM = 2; // 0x2 field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4 field public static final int PROGRAM_TYPE_INVALID = 0; // 0x0 field public static final int PROGRAM_TYPE_SXM = 7; // 0x7 field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 Loading Loading @@ -1985,10 +2020,11 @@ package android.hardware.radio { method public abstract void cancelAnnouncement(); method public abstract void close(); method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter); method public abstract boolean getMute(); method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>); method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); method public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); method public abstract deprecated java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); method public abstract boolean hasControl(); method public abstract deprecated boolean isAnalogForced(); method public abstract boolean isAntennaConnected(); Loading core/java/android/hardware/radio/ITuner.aidl +3 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading core/java/android/hardware/radio/ITunerCallback.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.hardware.radio; import android.hardware.radio.ProgramList; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; Loading @@ -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> Loading core/java/android/hardware/radio/ProgramList.aidl 0 → 100644 +23 −0 Original line number Diff line number Diff line /** * 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; core/java/android/hardware/radio/ProgramList.java 0 → 100644 +427 −0 Original line number Diff line number Diff line /** * 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; } } } Loading
api/system-current.txt +37 −1 Original line number Diff line number Diff line Loading @@ -1733,6 +1733,39 @@ package android.hardware.location { package android.hardware.radio { public final class ProgramList implements java.lang.AutoCloseable { method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener); method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); method public void close(); method public android.hardware.radio.RadioManager.ProgramInfo get(android.hardware.radio.ProgramSelector.Identifier); method public void registerListCallback(java.util.concurrent.Executor, android.hardware.radio.ProgramList.ListCallback); method public void registerListCallback(android.hardware.radio.ProgramList.ListCallback); method public void removeOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); method public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> toList(); method public void unregisterListCallback(android.hardware.radio.ProgramList.ListCallback); } public static final class ProgramList.Filter implements android.os.Parcelable { ctor public ProgramList.Filter(java.util.Set<java.lang.Integer>, java.util.Set<android.hardware.radio.ProgramSelector.Identifier>, boolean, boolean); method public boolean areCategoriesIncluded(); method public boolean areModificationsExcluded(); method public int describeContents(); method public java.util.Set<java.lang.Integer> getIdentifierTypes(); method public java.util.Set<android.hardware.radio.ProgramSelector.Identifier> getIdentifiers(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramList.Filter> CREATOR; } public static abstract class ProgramList.ListCallback { ctor public ProgramList.ListCallback(); method public void onItemChanged(android.hardware.radio.ProgramSelector.Identifier); method public void onItemRemoved(android.hardware.radio.ProgramSelector.Identifier); } public static abstract interface ProgramList.OnCompleteListener { method public abstract void onComplete(); } public final class ProgramSelector implements android.os.Parcelable { ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]); method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int); Loading @@ -1756,6 +1789,7 @@ package android.hardware.radio { field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc Loading @@ -1767,6 +1801,7 @@ package android.hardware.radio { field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6 field public static final int PROGRAM_TYPE_FM = 2; // 0x2 field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4 field public static final int PROGRAM_TYPE_INVALID = 0; // 0x0 field public static final int PROGRAM_TYPE_SXM = 7; // 0x7 field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 Loading Loading @@ -1985,10 +2020,11 @@ package android.hardware.radio { method public abstract void cancelAnnouncement(); method public abstract void close(); method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter); method public abstract boolean getMute(); method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>); method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); method public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); method public abstract deprecated java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); method public abstract boolean hasControl(); method public abstract deprecated boolean isAnalogForced(); method public abstract boolean isAntennaConnected(); Loading
core/java/android/hardware/radio/ITuner.aidl +3 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading
core/java/android/hardware/radio/ITunerCallback.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.hardware.radio; import android.hardware.radio.ProgramList; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; Loading @@ -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> Loading
core/java/android/hardware/radio/ProgramList.aidl 0 → 100644 +23 −0 Original line number Diff line number Diff line /** * 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;
core/java/android/hardware/radio/ProgramList.java 0 → 100644 +427 −0 Original line number Diff line number Diff line /** * 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; } } }