• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2017 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 com.android.server.broadcastradio.hal2;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
24 import android.hardware.broadcastradio.V2_0.Announcement;
25 import android.hardware.broadcastradio.V2_0.DabTableEntry;
26 import android.hardware.broadcastradio.V2_0.IAnnouncementListener;
27 import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
28 import android.hardware.broadcastradio.V2_0.ICloseHandle;
29 import android.hardware.broadcastradio.V2_0.ITunerCallback;
30 import android.hardware.broadcastradio.V2_0.ITunerSession;
31 import android.hardware.broadcastradio.V2_0.ProgramInfo;
32 import android.hardware.broadcastradio.V2_0.ProgramListChunk;
33 import android.hardware.broadcastradio.V2_0.ProgramSelector;
34 import android.hardware.broadcastradio.V2_0.Result;
35 import android.hardware.broadcastradio.V2_0.VendorKeyValue;
36 import android.hardware.radio.RadioManager;
37 import android.os.DeadObjectException;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.RemoteException;
41 import android.util.IndentingPrintWriter;
42 import android.util.MutableInt;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.GuardedBy;
46 import com.android.internal.annotations.VisibleForTesting;
47 
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Objects;
53 import java.util.Set;
54 import java.util.stream.Collectors;
55 
56 class RadioModule {
57     private static final String TAG = "BcRadio2Srv.module";
58     private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
59 
60     @NonNull private final IBroadcastRadio mService;
61     @NonNull public final RadioManager.ModuleProperties mProperties;
62 
63     private final Object mLock;
64     @NonNull private final Handler mHandler;
65     @NonNull private final RadioEventLogger mEventLogger;
66 
67     @GuardedBy("mLock")
68     private ITunerSession mHalTunerSession;
69 
70     // Tracks antenna state reported by HAL (if any).
71     @GuardedBy("mLock")
72     private Boolean mAntennaConnected = null;
73 
74     @GuardedBy("mLock")
75     private RadioManager.ProgramInfo mCurrentProgramInfo = null;
76 
77     @GuardedBy("mLock")
78     private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null);
79 
80     @GuardedBy("mLock")
81     private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters = null;
82 
83     // Callback registered with the HAL to relay callbacks to AIDL clients.
84     private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
85         @Override
86         public void onTuneFailed(int result, ProgramSelector programSelector) {
87             lockAndFireLater(() -> {
88                 android.hardware.radio.ProgramSelector csel =
89                         Convert.programSelectorFromHal(programSelector);
90                 fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
91             });
92         }
93 
94         @Override
95         public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
96             lockAndFireLater(() -> {
97                 mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo);
98                 fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mCurrentProgramInfo));
99             });
100         }
101 
102         @Override
103         public void onProgramListUpdated(ProgramListChunk programListChunk) {
104             lockAndFireLater(() -> {
105                 android.hardware.radio.ProgramList.Chunk chunk =
106                         Convert.programListChunkFromHal(programListChunk);
107                 mProgramInfoCache.filterAndApplyChunk(chunk);
108 
109                 for (TunerSession tunerSession : mAidlTunerSessions) {
110                     tunerSession.onMergedProgramListUpdateFromHal(chunk);
111                 }
112             });
113         }
114 
115         @Override
116         public void onAntennaStateChange(boolean connected) {
117             lockAndFireLater(() -> {
118                 mAntennaConnected = connected;
119                 fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
120             });
121         }
122 
123         @Override
124         public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
125             lockAndFireLater(() -> {
126                 Map<String, String> cparam = Convert.vendorInfoFromHal(parameters);
127                 fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
128             });
129         }
130     };
131 
132     // Collection of active AIDL tuner sessions created through openSession().
133     @GuardedBy("mLock")
134     private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();
135 
136     @VisibleForTesting
RadioModule(@onNull IBroadcastRadio service, @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock)137     RadioModule(@NonNull IBroadcastRadio service,
138             @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock) {
139         mProperties = Objects.requireNonNull(properties);
140         mService = Objects.requireNonNull(service);
141         mLock = Objects.requireNonNull(lock);
142         mHandler = new Handler(Looper.getMainLooper());
143         mEventLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
144     }
145 
tryLoadingModule(int idx, @NonNull String fqName, Object lock)146     public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName,
147             Object lock) {
148         try {
149             Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
150             IBroadcastRadio service = IBroadcastRadio.getService(fqName);
151             if (service == null) {
152                 Slog.w(TAG, "No service found for fqName " + fqName);
153                 return null;
154             }
155 
156             Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
157             service.getAmFmRegionConfig(false, (result, config) -> {
158                 if (result == Result.OK) amfmConfig.value = config;
159             });
160 
161             Mutable<List<DabTableEntry>> dabConfig = new Mutable<>();
162             service.getDabRegionConfig((result, config) -> {
163                 if (result == Result.OK) dabConfig.value = config;
164             });
165 
166             RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName,
167                     service.getProperties(), amfmConfig.value, dabConfig.value);
168 
169             return new RadioModule(service, prop, lock);
170         } catch (RemoteException ex) {
171             Slog.e(TAG, "Failed to load module " + fqName, ex);
172             return null;
173         }
174     }
175 
getService()176     public @NonNull IBroadcastRadio getService() {
177         return mService;
178     }
179 
openSession(@onNull android.hardware.radio.ITunerCallback userCb)180     public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
181             throws RemoteException {
182         mEventLogger.logRadioEvent("Open TunerSession");
183         synchronized (mLock) {
184             if (mHalTunerSession == null) {
185                 Mutable<ITunerSession> hwSession = new Mutable<>();
186                 mService.openSession(mHalTunerCallback, (result, session) -> {
187                     Convert.throwOnError("openSession", result);
188                     hwSession.value = session;
189                     mEventLogger.logRadioEvent("New HIDL 2.0 tuner session is opened");
190                 });
191                 mHalTunerSession = Objects.requireNonNull(hwSession.value);
192             }
193             TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb,
194                     mLock);
195             mAidlTunerSessions.add(tunerSession);
196 
197             // Propagate state to new client. Note: These callbacks are invoked while holding mLock
198             // to prevent race conditions with new callbacks from the HAL.
199             if (mAntennaConnected != null) {
200                 userCb.onAntennaState(mAntennaConnected);
201             }
202             if (mCurrentProgramInfo != null) {
203                 userCb.onCurrentProgramInfoChanged(mCurrentProgramInfo);
204             }
205 
206             return tunerSession;
207         }
208     }
209 
closeSessions(Integer error)210     public void closeSessions(Integer error) {
211         // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
212         // must be called without mAidlTunerSessions locked because it can call
213         // onTunerSessionClosed().
214         mEventLogger.logRadioEvent("Close TunerSessions");
215         TunerSession[] tunerSessions;
216         synchronized (mLock) {
217             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
218             mAidlTunerSessions.toArray(tunerSessions);
219             mAidlTunerSessions.clear();
220         }
221         for (TunerSession tunerSession : tunerSessions) {
222             tunerSession.close(error);
223         }
224     }
225 
226     private @Nullable android.hardware.radio.ProgramList.Filter
buildUnionOfTunerSessionFiltersLocked()227             buildUnionOfTunerSessionFiltersLocked() {
228         Set<Integer> idTypes = null;
229         Set<android.hardware.radio.ProgramSelector.Identifier> ids = null;
230         boolean includeCategories = false;
231         boolean excludeModifications = true;
232 
233         for (TunerSession tunerSession : mAidlTunerSessions) {
234             android.hardware.radio.ProgramList.Filter filter =
235                     tunerSession.getProgramListFilter();
236             if (filter == null) {
237                 continue;
238             }
239 
240             if (idTypes == null) {
241                 idTypes = new HashSet<>(filter.getIdentifierTypes());
242                 ids = new HashSet<>(filter.getIdentifiers());
243                 includeCategories = filter.areCategoriesIncluded();
244                 excludeModifications = filter.areModificationsExcluded();
245                 continue;
246             }
247             if (!idTypes.isEmpty()) {
248                 if (filter.getIdentifierTypes().isEmpty()) {
249                     idTypes.clear();
250                 } else {
251                     idTypes.addAll(filter.getIdentifierTypes());
252                 }
253             }
254 
255             if (!ids.isEmpty()) {
256                 if (filter.getIdentifiers().isEmpty()) {
257                     ids.clear();
258                 } else {
259                     ids.addAll(filter.getIdentifiers());
260                 }
261             }
262 
263             includeCategories |= filter.areCategoriesIncluded();
264             excludeModifications &= filter.areModificationsExcluded();
265         }
266 
267         return idTypes == null ? null : new android.hardware.radio.ProgramList.Filter(idTypes, ids,
268                 includeCategories, excludeModifications);
269     }
270 
onTunerSessionProgramListFilterChanged(@ullable TunerSession session)271     void onTunerSessionProgramListFilterChanged(@Nullable TunerSession session) {
272         synchronized (mLock) {
273             onTunerSessionProgramListFilterChangedLocked(session);
274         }
275     }
276 
onTunerSessionProgramListFilterChangedLocked(@ullable TunerSession session)277     private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) {
278         android.hardware.radio.ProgramList.Filter newFilter =
279                 buildUnionOfTunerSessionFiltersLocked();
280         if (newFilter == null) {
281             // If there are no AIDL clients remaining, we can stop updates from the HAL as well.
282             if (mUnionOfAidlProgramFilters == null) {
283                 return;
284             }
285             mUnionOfAidlProgramFilters = null;
286             try {
287                 mHalTunerSession.stopProgramListUpdates();
288             } catch (RemoteException ex) {
289                 Slog.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex);
290             }
291             return;
292         }
293 
294         // If the HAL filter doesn't change, we can immediately send an update to the AIDL
295         // client.
296         if (newFilter.equals(mUnionOfAidlProgramFilters)) {
297             if (session != null) {
298                 session.updateProgramInfoFromHalCache(mProgramInfoCache);
299             }
300             return;
301         }
302 
303         // Otherwise, update the HAL's filter, and AIDL clients will be updated when
304         // mHalTunerCallback.onProgramListUpdated() is called.
305         mUnionOfAidlProgramFilters = newFilter;
306         try {
307             int halResult = mHalTunerSession.startProgramListUpdates(Convert.programFilterToHal(
308                     newFilter));
309             Convert.throwOnError("startProgramListUpdates", halResult);
310         } catch (RemoteException ex) {
311             Slog.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex);
312         }
313     }
314 
onTunerSessionClosed(TunerSession tunerSession)315     void onTunerSessionClosed(TunerSession tunerSession) {
316         synchronized (mLock) {
317             onTunerSessionsClosedLocked(tunerSession);
318         }
319     }
320 
onTunerSessionsClosedLocked(TunerSession... tunerSessions)321     private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) {
322         for (TunerSession tunerSession : tunerSessions) {
323             mAidlTunerSessions.remove(tunerSession);
324         }
325         onTunerSessionProgramListFilterChanged(null);
326         if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
327             mEventLogger.logRadioEvent("Closing HAL tuner session");
328             try {
329                 mHalTunerSession.close();
330             } catch (RemoteException ex) {
331                 Slog.e(TAG, "mHalTunerSession.close() failed: ", ex);
332             }
333             mHalTunerSession = null;
334         }
335     }
336 
337     // add to mHandler queue, but ensure the runnable holds mLock when it gets executed
lockAndFireLater(Runnable r)338     private void lockAndFireLater(Runnable r) {
339         mHandler.post(() -> {
340             synchronized (mLock) {
341                 r.run();
342             }
343         });
344     }
345 
346     interface AidlCallbackRunnable {
run(android.hardware.radio.ITunerCallback callback)347         void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
348     }
349 
350     // Invokes runnable with each TunerSession currently open.
fanoutAidlCallback(AidlCallbackRunnable runnable)351     void fanoutAidlCallback(AidlCallbackRunnable runnable) {
352         lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable));
353     }
354 
fanoutAidlCallbackLocked(AidlCallbackRunnable runnable)355     private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
356         List<TunerSession> deadSessions = null;
357         for (TunerSession tunerSession : mAidlTunerSessions) {
358             try {
359                 runnable.run(tunerSession.mCallback);
360             } catch (DeadObjectException ex) {
361                 // The other side died without calling close(), so just purge it from our records.
362                 Slog.e(TAG, "Removing dead TunerSession");
363                 if (deadSessions == null) {
364                     deadSessions = new ArrayList<>();
365                 }
366                 deadSessions.add(tunerSession);
367             } catch (RemoteException ex) {
368                 Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
369             }
370         }
371         if (deadSessions != null) {
372             onTunerSessionsClosedLocked(deadSessions.toArray(
373                     new TunerSession[deadSessions.size()]));
374         }
375     }
376 
addAnnouncementListener(@onNull int[] enabledTypes, @NonNull android.hardware.radio.IAnnouncementListener listener)377     public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
378             @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
379         mEventLogger.logRadioEvent("Add AnnouncementListener");
380         ArrayList<Byte> enabledList = new ArrayList<>();
381         for (int type : enabledTypes) {
382             enabledList.add((byte)type);
383         }
384 
385         MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
386         Mutable<ICloseHandle> hwCloseHandle = new Mutable<>();
387         IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
388             public void onListUpdated(ArrayList<Announcement> hwAnnouncements)
389                     throws RemoteException {
390                 listener.onListUpdated(hwAnnouncements.stream().
391                     map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
392             }
393         };
394 
395         synchronized (mLock) {
396             mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
397                 halResult.value = result;
398                 hwCloseHandle.value = closeHnd;
399             });
400         }
401         Convert.throwOnError("addAnnouncementListener", halResult.value);
402 
403         return new android.hardware.radio.ICloseHandle.Stub() {
404             public void close() {
405                 try {
406                     hwCloseHandle.value.close();
407                 } catch (RemoteException ex) {
408                     Slog.e(TAG, "Failed closing announcement listener", ex);
409                 }
410                 hwCloseHandle.value = null;
411             }
412         };
413     }
414 
415     Bitmap getImage(int id) {
416         mEventLogger.logRadioEvent("Get image for id %d", id);
417         if (id == 0) throw new IllegalArgumentException("Image ID is missing");
418 
419         byte[] rawImage;
420         synchronized (mLock) {
421             List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
422             rawImage = new byte[rawList.size()];
423             for (int i = 0; i < rawList.size(); i++) {
424                 rawImage[i] = rawList.get(i);
425             }
426         }
427 
428         if (rawImage == null || rawImage.length == 0) return null;
429 
430         return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
431     }
432 
433     void dumpInfo(IndentingPrintWriter pw) {
434         pw.printf("RadioModule\n");
435         pw.increaseIndent();
436         synchronized (mLock) {
437             pw.printf("BroadcastRadioService: %s\n", mService);
438             pw.printf("Properties: %s\n", mProperties);
439             pw.printf("HIDL2.0 HAL TunerSession: %s\n", mHalTunerSession);
440             pw.printf("Is antenna connected? ");
441             if (mAntennaConnected == null) {
442                 pw.printf("null\n");
443             } else {
444                 pw.printf("%s\n", mAntennaConnected ? "Yes" : "No");
445             }
446             pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo);
447             pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
448             pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters);
449             pw.printf("AIDL TunerSessions:\n");
450             pw.increaseIndent();
451             for (TunerSession aidlTunerSession : mAidlTunerSessions) {
452                 aidlTunerSession.dumpInfo(pw);
453             }
454             pw.decreaseIndent();
455         }
456         pw.printf("Radio module events:\n");
457         pw.increaseIndent();
458         mEventLogger.dump(pw);
459         pw.decreaseIndent();
460         pw.decreaseIndent();
461     }
462 }
463