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