blob: 69f927375dd9b9ffdee6ab52cefc57d11617bc08 [file] [log] [blame]
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -08001/**
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
17package android.hardware.radio;
18
19import android.annotation.CallbackExecutor;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.SystemApi;
23import android.os.Parcel;
24import android.os.Parcelable;
25
26import java.util.ArrayList;
27import java.util.Collections;
28import java.util.HashMap;
29import java.util.HashSet;
30import java.util.List;
31import java.util.Map;
32import java.util.Objects;
33import java.util.Set;
34import java.util.concurrent.Executor;
35import java.util.stream.Collectors;
36
37/**
38 * @hide
39 */
40@SystemApi
41public 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 */
Tomasz Wasilczykd0c78f92018-03-28 17:50:08 -0700266 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 */
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800277 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
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700307 public static final @android.annotation.NonNull Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() {
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800308 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
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700412 public static final @android.annotation.NonNull Parcelable.Creator<Chunk> CREATOR = new Parcelable.Creator<Chunk>() {
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800413 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}