Home | History | Annotate | Download | only in radio
      1 /**
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.hardware.radio;
     18 
     19 import android.annotation.CallbackExecutor;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.annotation.SystemApi;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 
     26 import java.util.ArrayList;
     27 import java.util.Collections;
     28 import java.util.HashMap;
     29 import java.util.HashSet;
     30 import java.util.List;
     31 import java.util.Map;
     32 import java.util.Objects;
     33 import java.util.Set;
     34 import java.util.concurrent.Executor;
     35 import java.util.stream.Collectors;
     36 
     37 /**
     38  * @hide
     39  */
     40 @SystemApi
     41 public final class ProgramList implements AutoCloseable {
     42 
     43     private final Object mLock = new Object();
     44     private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
     45             new HashMap<>();
     46 
     47     private final List<ListCallback> mListCallbacks = new ArrayList<>();
     48     private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
     49     private OnCloseListener mOnCloseListener;
     50     private boolean mIsClosed = false;
     51     private boolean mIsComplete = false;
     52 
     53     ProgramList() {}
     54 
     55     /**
     56      * Callback for list change operations.
     57      */
     58     public abstract static class ListCallback {
     59         /**
     60          * Called when item was modified or added to the list.
     61          */
     62         public void onItemChanged(@NonNull ProgramSelector.Identifier id) { }
     63 
     64         /**
     65          * Called when item was removed from the list.
     66          */
     67         public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { }
     68     }
     69 
     70     /**
     71      * Listener of list complete event.
     72      */
     73     public interface OnCompleteListener {
     74         /**
     75          * Called when the list turned complete (i.e. when the scan process
     76          * came to an end).
     77          */
     78         void onComplete();
     79     }
     80 
     81     interface OnCloseListener {
     82         void onClose();
     83     }
     84 
     85     /**
     86      * Registers list change callback with executor.
     87      */
     88     public void registerListCallback(@NonNull @CallbackExecutor Executor executor,
     89             @NonNull ListCallback callback) {
     90         registerListCallback(new ListCallback() {
     91             public void onItemChanged(@NonNull ProgramSelector.Identifier id) {
     92                 executor.execute(() -> callback.onItemChanged(id));
     93             }
     94 
     95             public void onItemRemoved(@NonNull ProgramSelector.Identifier id) {
     96                 executor.execute(() -> callback.onItemRemoved(id));
     97             }
     98         });
     99     }
    100 
    101     /**
    102      * Registers list change callback.
    103      */
    104     public void registerListCallback(@NonNull ListCallback callback) {
    105         synchronized (mLock) {
    106             if (mIsClosed) return;
    107             mListCallbacks.add(Objects.requireNonNull(callback));
    108         }
    109     }
    110 
    111     /**
    112      * Unregisters list change callback.
    113      */
    114     public void unregisterListCallback(@NonNull ListCallback callback) {
    115         synchronized (mLock) {
    116             if (mIsClosed) return;
    117             mListCallbacks.remove(Objects.requireNonNull(callback));
    118         }
    119     }
    120 
    121     /**
    122      * Adds list complete event listener with executor.
    123      */
    124     public void addOnCompleteListener(@NonNull @CallbackExecutor Executor executor,
    125             @NonNull OnCompleteListener listener) {
    126         addOnCompleteListener(() -> executor.execute(listener::onComplete));
    127     }
    128 
    129     /**
    130      * Adds list complete event listener.
    131      */
    132     public void addOnCompleteListener(@NonNull OnCompleteListener listener) {
    133         synchronized (mLock) {
    134             if (mIsClosed) return;
    135             mOnCompleteListeners.add(Objects.requireNonNull(listener));
    136             if (mIsComplete) listener.onComplete();
    137         }
    138     }
    139 
    140     /**
    141      * Removes list complete event listener.
    142      */
    143     public void removeOnCompleteListener(@NonNull OnCompleteListener listener) {
    144         synchronized (mLock) {
    145             if (mIsClosed) return;
    146             mOnCompleteListeners.remove(Objects.requireNonNull(listener));
    147         }
    148     }
    149 
    150     void setOnCloseListener(@Nullable OnCloseListener listener) {
    151         synchronized (mLock) {
    152             if (mOnCloseListener != null) {
    153                 throw new IllegalStateException("Close callback is already set");
    154             }
    155             mOnCloseListener = listener;
    156         }
    157     }
    158 
    159     /**
    160      * Disables list updates and releases all resources.
    161      */
    162     public void close() {
    163         synchronized (mLock) {
    164             if (mIsClosed) return;
    165             mIsClosed = true;
    166             mPrograms.clear();
    167             mListCallbacks.clear();
    168             mOnCompleteListeners.clear();
    169             if (mOnCloseListener != null) {
    170                 mOnCloseListener.onClose();
    171                 mOnCloseListener = null;
    172             }
    173         }
    174     }
    175 
    176     void apply(@NonNull Chunk chunk) {
    177         synchronized (mLock) {
    178             if (mIsClosed) return;
    179 
    180             mIsComplete = false;
    181 
    182             if (chunk.isPurge()) {
    183                 new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
    184             }
    185 
    186             chunk.getRemoved().stream().forEach(id -> removeLocked(id));
    187             chunk.getModified().stream().forEach(info -> putLocked(info));
    188 
    189             if (chunk.isComplete()) {
    190                 mIsComplete = true;
    191                 mOnCompleteListeners.forEach(cb -> cb.onComplete());
    192             }
    193         }
    194     }
    195 
    196     private void putLocked(@NonNull RadioManager.ProgramInfo value) {
    197         ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
    198         mPrograms.put(Objects.requireNonNull(key), value);
    199         ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
    200         mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
    201     }
    202 
    203     private void removeLocked(@NonNull ProgramSelector.Identifier key) {
    204         RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
    205         if (removed == null) return;
    206         ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
    207         mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
    208     }
    209 
    210     /**
    211      * Converts the program list in its current shape to the static List<>.
    212      *
    213      * @return the new List<> object; it won't receive any further updates
    214      */
    215     public @NonNull List<RadioManager.ProgramInfo> toList() {
    216         synchronized (mLock) {
    217             return mPrograms.values().stream().collect(Collectors.toList());
    218         }
    219     }
    220 
    221     /**
    222      * Returns the program with a specified primary identifier.
    223      *
    224      * @param id primary identifier of a program to fetch
    225      * @return the program info, or null if there is no such program on the list
    226      */
    227     public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
    228         synchronized (mLock) {
    229             return mPrograms.get(Objects.requireNonNull(id));
    230         }
    231     }
    232 
    233     /**
    234      * Filter for the program list.
    235      */
    236     public static final class Filter implements Parcelable {
    237         private final @NonNull Set<Integer> mIdentifierTypes;
    238         private final @NonNull Set<ProgramSelector.Identifier> mIdentifiers;
    239         private final boolean mIncludeCategories;
    240         private final boolean mExcludeModifications;
    241         private final @Nullable Map<String, String> mVendorFilter;
    242 
    243         /**
    244          * Constructor of program list filter.
    245          *
    246          * Arrays passed to this constructor become owned by this object, do not modify them later.
    247          *
    248          * @param identifierTypes see getIdentifierTypes()
    249          * @param identifiers see getIdentifiers()
    250          * @param includeCategories see areCategoriesIncluded()
    251          * @param excludeModifications see areModificationsExcluded()
    252          */
    253         public Filter(@NonNull Set<Integer> identifierTypes,
    254                 @NonNull Set<ProgramSelector.Identifier> identifiers,
    255                 boolean includeCategories, boolean excludeModifications) {
    256             mIdentifierTypes = Objects.requireNonNull(identifierTypes);
    257             mIdentifiers = Objects.requireNonNull(identifiers);
    258             mIncludeCategories = includeCategories;
    259             mExcludeModifications = excludeModifications;
    260             mVendorFilter = null;
    261         }
    262 
    263         /**
    264          * @hide for framework use only
    265          */
    266         public Filter() {
    267             mIdentifierTypes = Collections.emptySet();
    268             mIdentifiers = Collections.emptySet();
    269             mIncludeCategories = false;
    270             mExcludeModifications = false;
    271             mVendorFilter = null;
    272         }
    273 
    274         /**
    275          * @hide for framework use only
    276          */
    277         public Filter(@Nullable Map<String, String> vendorFilter) {
    278             mIdentifierTypes = Collections.emptySet();
    279             mIdentifiers = Collections.emptySet();
    280             mIncludeCategories = false;
    281             mExcludeModifications = false;
    282             mVendorFilter = vendorFilter;
    283         }
    284 
    285         private Filter(@NonNull Parcel in) {
    286             mIdentifierTypes = Utils.createIntSet(in);
    287             mIdentifiers = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
    288             mIncludeCategories = in.readByte() != 0;
    289             mExcludeModifications = in.readByte() != 0;
    290             mVendorFilter = Utils.readStringMap(in);
    291         }
    292 
    293         @Override
    294         public void writeToParcel(Parcel dest, int flags) {
    295             Utils.writeIntSet(dest, mIdentifierTypes);
    296             Utils.writeSet(dest, mIdentifiers);
    297             dest.writeByte((byte) (mIncludeCategories ? 1 : 0));
    298             dest.writeByte((byte) (mExcludeModifications ? 1 : 0));
    299             Utils.writeStringMap(dest, mVendorFilter);
    300         }
    301 
    302         @Override
    303         public int describeContents() {
    304             return 0;
    305         }
    306 
    307         public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() {
    308             public Filter createFromParcel(Parcel in) {
    309                 return new Filter(in);
    310             }
    311 
    312             public Filter[] newArray(int size) {
    313                 return new Filter[size];
    314             }
    315         };
    316 
    317         /**
    318          * @hide for framework use only
    319          */
    320         public Map<String, String> getVendorFilter() {
    321             return mVendorFilter;
    322         }
    323 
    324         /**
    325          * Returns the list of identifier types that satisfy the filter.
    326          *
    327          * If the program list entry contains at least one identifier of the type
    328          * listed, it satisfies this condition.
    329          *
    330          * Empty list means no filtering on identifier type.
    331          *
    332          * @return the list of accepted identifier types, must not be modified
    333          */
    334         public @NonNull Set<Integer> getIdentifierTypes() {
    335             return mIdentifierTypes;
    336         }
    337 
    338         /**
    339          * Returns the list of identifiers that satisfy the filter.
    340          *
    341          * If the program list entry contains at least one listed identifier,
    342          * it satisfies this condition.
    343          *
    344          * Empty list means no filtering on identifier.
    345          *
    346          * @return the list of accepted identifiers, must not be modified
    347          */
    348         public @NonNull Set<ProgramSelector.Identifier> getIdentifiers() {
    349             return mIdentifiers;
    350         }
    351 
    352         /**
    353          * Checks, if non-tunable entries that define tree structure on the
    354          * program list (i.e. DAB ensembles) should be included.
    355          */
    356         public boolean areCategoriesIncluded() {
    357             return mIncludeCategories;
    358         }
    359 
    360         /**
    361          * Checks, if updates on entry modifications should be disabled.
    362          *
    363          * If true, 'modified' vector of ProgramListChunk must contain list
    364          * additions only. Once the program is added to the list, it's not
    365          * updated anymore.
    366          */
    367         public boolean areModificationsExcluded() {
    368             return mExcludeModifications;
    369         }
    370     }
    371 
    372     /**
    373      * @hide This is a transport class used for internal communication between
    374      *       Broadcast Radio Service and RadioManager.
    375      *       Do not use it directly.
    376      */
    377     public static final class Chunk implements Parcelable {
    378         private final boolean mPurge;
    379         private final boolean mComplete;
    380         private final @NonNull Set<RadioManager.ProgramInfo> mModified;
    381         private final @NonNull Set<ProgramSelector.Identifier> mRemoved;
    382 
    383         public Chunk(boolean purge, boolean complete,
    384                 @Nullable Set<RadioManager.ProgramInfo> modified,
    385                 @Nullable Set<ProgramSelector.Identifier> removed) {
    386             mPurge = purge;
    387             mComplete = complete;
    388             mModified = (modified != null) ? modified : Collections.emptySet();
    389             mRemoved = (removed != null) ? removed : Collections.emptySet();
    390         }
    391 
    392         private Chunk(@NonNull Parcel in) {
    393             mPurge = in.readByte() != 0;
    394             mComplete = in.readByte() != 0;
    395             mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR);
    396             mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
    397         }
    398 
    399         @Override
    400         public void writeToParcel(Parcel dest, int flags) {
    401             dest.writeByte((byte) (mPurge ? 1 : 0));
    402             dest.writeByte((byte) (mComplete ? 1 : 0));
    403             Utils.writeSet(dest, mModified);
    404             Utils.writeSet(dest, mRemoved);
    405         }
    406 
    407         @Override
    408         public int describeContents() {
    409             return 0;
    410         }
    411 
    412         public static final Parcelable.Creator<Chunk> CREATOR = new Parcelable.Creator<Chunk>() {
    413             public Chunk createFromParcel(Parcel in) {
    414                 return new Chunk(in);
    415             }
    416 
    417             public Chunk[] newArray(int size) {
    418                 return new Chunk[size];
    419             }
    420         };
    421 
    422         public boolean isPurge() {
    423             return mPurge;
    424         }
    425 
    426         public boolean isComplete() {
    427             return mComplete;
    428         }
    429 
    430         public @NonNull Set<RadioManager.ProgramInfo> getModified() {
    431             return mModified;
    432         }
    433 
    434         public @NonNull Set<ProgramSelector.Identifier> getRemoved() {
    435             return mRemoved;
    436         }
    437     }
    438 }
    439