1 /*
2  * Copyright (C) 2022 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.aidl;
18 
19 import android.annotation.Nullable;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapFactory;
22 import android.hardware.broadcastradio.AmFmRegionConfig;
23 import android.hardware.broadcastradio.Announcement;
24 import android.hardware.broadcastradio.DabTableEntry;
25 import android.hardware.broadcastradio.IAnnouncementListener;
26 import android.hardware.broadcastradio.IBroadcastRadio;
27 import android.hardware.broadcastradio.ICloseHandle;
28 import android.hardware.broadcastradio.ITunerCallback;
29 import android.hardware.broadcastradio.ProgramInfo;
30 import android.hardware.broadcastradio.ProgramListChunk;
31 import android.hardware.broadcastradio.ProgramSelector;
32 import android.hardware.broadcastradio.VendorKeyValue;
33 import android.hardware.radio.RadioManager;
34 import android.os.DeadObjectException;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.RemoteException;
39 import android.os.UserHandle;
40 import android.util.ArraySet;
41 import android.util.IndentingPrintWriter;
42 
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.server.broadcastradio.RadioEventLogger;
46 import com.android.server.broadcastradio.RadioServiceUserController;
47 import com.android.server.utils.Slogf;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Objects;
53 import java.util.Set;
54 
55 final class RadioModule {
56     private static final String TAG = "BcRadioAidlSrv.module";
57     private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
58 
59     private final IBroadcastRadio mService;
60 
61     private final Object mLock = new Object();
62     private final Handler mHandler;
63     private final RadioEventLogger mLogger;
64     private final RadioManager.ModuleProperties mProperties;
65 
66     /**
67      * Tracks antenna state reported by HAL (if any).
68      */
69     @GuardedBy("mLock")
70     private Boolean mAntennaConnected;
71 
72     @GuardedBy("mLock")
73     private RadioManager.ProgramInfo mCurrentProgramInfo;
74 
75     @GuardedBy("mLock")
76     private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null);
77 
78     @GuardedBy("mLock")
79     private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters;
80 
81     /**
82      * Set of active AIDL tuner sessions created through openSession().
83      */
84     @GuardedBy("mLock")
85     private final ArraySet<TunerSession> mAidlTunerSessions = new ArraySet<>();
86 
87     /**
88      * Callback registered with the HAL to relay callbacks to AIDL clients.
89      */
90     private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
91         @Override
92         public int getInterfaceVersion() {
93             return this.VERSION;
94         }
95 
96         @Override
97         public String getInterfaceHash() {
98             return this.HASH;
99         }
100 
101         public void onTuneFailed(int result, ProgramSelector programSelector) {
102             fireLater(() -> {
103                 android.hardware.radio.ProgramSelector csel =
104                         ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
105                 int tunerResult = ConversionUtils.halResultToTunerResult(result);
106                 synchronized (mLock) {
107                     fanoutAidlCallbackLocked((cb, uid) -> {
108                         if (csel != null && !ConversionUtils
109                                 .programSelectorMeetsSdkVersionRequirement(csel, uid)) {
110                             Slogf.e(TAG, "onTuneFailed: cannot send program selector "
111                                     + "requiring higher target SDK version");
112                             return;
113                         }
114                         cb.onTuneFailed(tunerResult, csel);
115                     });
116                 }
117             });
118         }
119 
120         @Override
121         public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
122             fireLater(() -> {
123                 RadioManager.ProgramInfo currentProgramInfo =
124                         ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
125                 Objects.requireNonNull(currentProgramInfo,
126                         "Program info from AIDL HAL is invalid");
127                 synchronized (mLock) {
128                     mCurrentProgramInfo = currentProgramInfo;
129                     fanoutAidlCallbackLocked((cb, uid) -> {
130                         if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
131                                 currentProgramInfo, uid)) {
132                             Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send "
133                                     + "program info requiring higher target SDK version");
134                             return;
135                         }
136                         cb.onCurrentProgramInfoChanged(currentProgramInfo);
137                     });
138                 }
139             });
140         }
141 
142         @Override
143         public void onProgramListUpdated(ProgramListChunk programListChunk) {
144             fireLater(() -> {
145                 synchronized (mLock) {
146                     mProgramInfoCache.filterAndApplyChunk(programListChunk);
147 
148                     for (int i = 0; i < mAidlTunerSessions.size(); i++) {
149                         mAidlTunerSessions.valueAt(i).onMergedProgramListUpdateFromHal(
150                                 programListChunk);
151                     }
152                 }
153             });
154         }
155 
156         @Override
157         public void onAntennaStateChange(boolean connected) {
158             fireLater(() -> {
159                 synchronized (mLock) {
160                     mAntennaConnected = connected;
161                     fanoutAidlCallbackLocked((cb, uid) -> cb.onAntennaState(connected));
162                 }
163             });
164         }
165 
166         @Override
167         public void onConfigFlagUpdated(int flag, boolean value) {
168             fireLater(() -> {
169                 synchronized (mLock) {
170                     fanoutAidlCallbackLocked((cb, uid) -> {
171                         if (!ConversionUtils.configFlagMeetsSdkVersionRequirement(flag, uid)) {
172                             Slogf.e(TAG, "onConfigFlagUpdated: cannot send program info "
173                                     + "requiring higher target SDK version");
174                             return;
175                         }
176                         cb.onConfigFlagUpdated(flag, value);
177                     });
178                 }
179             });
180         }
181 
182         @Override
183         public void onParametersUpdated(VendorKeyValue[] parameters) {
184             fireLater(() -> {
185                 synchronized (mLock) {
186                     Map<String, String> cparam =
187                             ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
188                     fanoutAidlCallbackLocked((cb, uid) -> {
189                         cb.onParametersUpdated(cparam);
190                     });
191                 }
192             });
193         }
194     };
195 
196     @VisibleForTesting
RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties)197     RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties) {
198         mProperties = Objects.requireNonNull(properties, "properties cannot be null");
199         mService = Objects.requireNonNull(service, "service cannot be null");
200         mHandler = new Handler(Looper.getMainLooper());
201         mLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
202     }
203 
204     @Nullable
tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder)205     static RadioModule tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder) {
206         try {
207             Slogf.i(TAG, "Try loading module for module id = %d, module name = %s",
208                     moduleId, moduleName);
209             IBroadcastRadio service = IBroadcastRadio.Stub
210                     .asInterface(serviceBinder);
211             if (service == null) {
212                 Slogf.w(TAG, "Module %s is null", moduleName);
213                 return null;
214             }
215 
216             AmFmRegionConfig amfmConfig;
217             try {
218                 amfmConfig = service.getAmFmRegionConfig(/* full= */ false);
219             } catch (RuntimeException ex) {
220                 Slogf.i(TAG, "Module %s does not has AMFM config", moduleName);
221                 amfmConfig = null;
222             }
223 
224             DabTableEntry[] dabConfig;
225             try {
226                 dabConfig = service.getDabRegionConfig();
227             } catch (RuntimeException ex) {
228                 Slogf.i(TAG, "Module %s does not has DAB config", moduleName);
229                 dabConfig = null;
230             }
231 
232             RadioManager.ModuleProperties prop = ConversionUtils.propertiesFromHalProperties(
233                     moduleId, moduleName, service.getProperties(), amfmConfig, dabConfig);
234 
235             return new RadioModule(service, prop);
236         } catch (RemoteException ex) {
237             Slogf.e(TAG, ex, "Failed to load module %s", moduleName);
238             return null;
239         }
240     }
241 
getService()242     IBroadcastRadio getService() {
243         return mService;
244     }
245 
getProperties()246     RadioManager.ModuleProperties getProperties() {
247         return mProperties;
248     }
249 
openSession(android.hardware.radio.ITunerCallback userCb)250     TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
251             throws RemoteException {
252         mLogger.logRadioEvent("Open TunerSession");
253         TunerSession tunerSession;
254         Boolean antennaConnected;
255         RadioManager.ProgramInfo currentProgramInfo;
256         synchronized (mLock) {
257             boolean isFirstTunerSession = mAidlTunerSessions.isEmpty();
258             tunerSession = new TunerSession(this, mService, userCb);
259             mAidlTunerSessions.add(tunerSession);
260             antennaConnected = mAntennaConnected;
261             currentProgramInfo = mCurrentProgramInfo;
262             if (isFirstTunerSession) {
263                 mService.setTunerCallback(mHalTunerCallback);
264             }
265         }
266         // Propagate state to new client.
267         // Note: These callbacks are invoked while holding mLock to prevent race conditions
268         // with new callbacks from the HAL.
269         if (antennaConnected != null) {
270             userCb.onAntennaState(antennaConnected);
271         }
272         if (currentProgramInfo != null) {
273             userCb.onCurrentProgramInfoChanged(currentProgramInfo);
274         }
275 
276         return tunerSession;
277     }
278 
closeSessions(int error)279     void closeSessions(int error) {
280         mLogger.logRadioEvent("Close TunerSessions %d", error);
281         // TunerSession.close() must be called without mAidlTunerSessions locked because
282         // it can call onTunerSessionClosed(). Therefore, the contents of mAidlTunerSessions
283         // are copied into a local array here.
284         TunerSession[] tunerSessions;
285         synchronized (mLock) {
286             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
287             mAidlTunerSessions.toArray(tunerSessions);
288         }
289 
290         for (TunerSession tunerSession : tunerSessions) {
291             try {
292                 tunerSession.close(error);
293             } catch (Exception e) {
294                 Slogf.e(TAG, "Failed to close TunerSession %s: %s", tunerSession, e);
295             }
296         }
297     }
298 
299     @GuardedBy("mLock")
300     @Nullable
buildUnionOfTunerSessionFiltersLocked()301     private android.hardware.radio.ProgramList.Filter buildUnionOfTunerSessionFiltersLocked() {
302         Set<Integer> idTypes = null;
303         Set<android.hardware.radio.ProgramSelector.Identifier> ids = null;
304         boolean includeCategories = false;
305         boolean excludeModifications = true;
306 
307         for (int i = 0; i < mAidlTunerSessions.size(); i++) {
308             android.hardware.radio.ProgramList.Filter filter =
309                     mAidlTunerSessions.valueAt(i).getProgramListFilter();
310             if (filter == null) {
311                 continue;
312             }
313 
314             if (idTypes == null) {
315                 idTypes = new ArraySet<>(filter.getIdentifierTypes());
316                 ids = new ArraySet<>(filter.getIdentifiers());
317                 includeCategories = filter.areCategoriesIncluded();
318                 excludeModifications = filter.areModificationsExcluded();
319                 continue;
320             }
321             if (!idTypes.isEmpty()) {
322                 if (filter.getIdentifierTypes().isEmpty()) {
323                     idTypes.clear();
324                 } else {
325                     idTypes.addAll(filter.getIdentifierTypes());
326                 }
327             }
328 
329             if (!ids.isEmpty()) {
330                 if (filter.getIdentifiers().isEmpty()) {
331                     ids.clear();
332                 } else {
333                     ids.addAll(filter.getIdentifiers());
334                 }
335             }
336 
337             includeCategories |= filter.areCategoriesIncluded();
338             excludeModifications &= filter.areModificationsExcluded();
339         }
340 
341         return idTypes == null ? null : new android.hardware.radio.ProgramList.Filter(idTypes, ids,
342                 includeCategories, excludeModifications);
343     }
344 
onTunerSessionProgramListFilterChanged(@ullable TunerSession session)345     void onTunerSessionProgramListFilterChanged(@Nullable TunerSession session) {
346         synchronized (mLock) {
347             onTunerSessionProgramListFilterChangedLocked(session);
348         }
349     }
350 
351     @GuardedBy("mLock")
onTunerSessionProgramListFilterChangedLocked(@ullable TunerSession session)352     private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) {
353         android.hardware.radio.ProgramList.Filter newFilter =
354                 buildUnionOfTunerSessionFiltersLocked();
355         if (newFilter == null) {
356             // If there are no AIDL clients remaining, we can stop updates from the HAL as well.
357             if (mUnionOfAidlProgramFilters == null) {
358                 return;
359             }
360             mUnionOfAidlProgramFilters = null;
361             try {
362                 mService.stopProgramListUpdates();
363             } catch (RemoteException ex) {
364                 Slogf.e(TAG, ex, "mHalTunerSession.stopProgramListUpdates() failed");
365             }
366             return;
367         }
368 
369         synchronized (mLock) {
370             // If the HAL filter doesn't change, we can immediately send an update to the AIDL
371             // client.
372             if (newFilter.equals(mUnionOfAidlProgramFilters)) {
373                 if (session != null) {
374                     session.updateProgramInfoFromHalCache(mProgramInfoCache);
375                 }
376                 return;
377             }
378 
379             // Otherwise, update the HAL's filter, and AIDL clients will be updated when
380             // mHalTunerCallback.onProgramListUpdated() is called.
381             mUnionOfAidlProgramFilters = newFilter;
382         }
383         try {
384             mService.startProgramListUpdates(
385                     ConversionUtils.filterToHalProgramFilter(newFilter));
386         } catch (RuntimeException ex) {
387             throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates");
388         } catch (RemoteException ex) {
389             Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed");
390         }
391     }
392 
onTunerSessionClosed(TunerSession tunerSession)393     void onTunerSessionClosed(TunerSession tunerSession) {
394         synchronized (mLock) {
395             onTunerSessionsClosedLocked(tunerSession);
396         }
397     }
398 
399     @GuardedBy("mLock")
onTunerSessionsClosedLocked(TunerSession... tunerSessions)400     private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) {
401         for (TunerSession tunerSession : tunerSessions) {
402             mAidlTunerSessions.remove(tunerSession);
403         }
404         onTunerSessionProgramListFilterChanged(null);
405         if (mAidlTunerSessions.isEmpty()) {
406             try {
407                 mService.unsetTunerCallback();
408             } catch (RemoteException ex) {
409                 Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d",
410                         mProperties.getId());
411             }
412         }
413     }
414 
415     // add to mHandler queue
fireLater(Runnable r)416     private void fireLater(Runnable r) {
417         mHandler.post(() -> r.run());
418     }
419 
420     interface AidlCallbackRunnable {
run(android.hardware.radio.ITunerCallback callback, int uid)421         void run(android.hardware.radio.ITunerCallback callback, int uid)
422                 throws RemoteException;
423     }
424 
425     // Invokes runnable with each TunerSession currently open.
fanoutAidlCallback(AidlCallbackRunnable runnable)426     void fanoutAidlCallback(AidlCallbackRunnable runnable) {
427         fireLater(() -> {
428             synchronized (mLock) {
429                 fanoutAidlCallbackLocked(runnable);
430             }
431         });
432     }
433 
434     @GuardedBy("mLock")
fanoutAidlCallbackLocked(AidlCallbackRunnable runnable)435     private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
436         int currentUserId = RadioServiceUserController.getCurrentUser();
437         List<TunerSession> deadSessions = null;
438         for (int i = 0; i < mAidlTunerSessions.size(); i++) {
439             if (mAidlTunerSessions.valueAt(i).mUserId != currentUserId
440                     && mAidlTunerSessions.valueAt(i).mUserId != UserHandle.USER_SYSTEM) {
441                 continue;
442             }
443             try {
444                 runnable.run(mAidlTunerSessions.valueAt(i).mCallback,
445                         mAidlTunerSessions.valueAt(i).getUid());
446             } catch (DeadObjectException ex) {
447                 // The other side died without calling close(), so just purge it from our records.
448                 Slogf.e(TAG, "Removing dead TunerSession");
449                 if (deadSessions == null) {
450                     deadSessions = new ArrayList<>();
451                 }
452                 deadSessions.add(mAidlTunerSessions.valueAt(i));
453             } catch (RemoteException ex) {
454                 Slogf.e(TAG, ex, "Failed to invoke ITunerCallback");
455             }
456         }
457         if (deadSessions != null) {
458             onTunerSessionsClosedLocked(deadSessions.toArray(
459                     new TunerSession[deadSessions.size()]));
460         }
461     }
462 
addAnnouncementListener( android.hardware.radio.IAnnouncementListener listener, int[] enabledTypes)463     android.hardware.radio.ICloseHandle addAnnouncementListener(
464             android.hardware.radio.IAnnouncementListener listener,
465             int[] enabledTypes) throws RemoteException {
466         mLogger.logRadioEvent("Add AnnouncementListener");
467         byte[] enabledList = new byte[enabledTypes.length];
468         for (int index = 0; index < enabledList.length; index++) {
469             enabledList[index] = (byte) enabledTypes[index];
470         }
471 
472         final ICloseHandle[] hwCloseHandle = {null};
473         IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
474             public int getInterfaceVersion() {
475                 return this.VERSION;
476             }
477 
478             public String getInterfaceHash() {
479                 return this.HASH;
480             }
481 
482             public void onListUpdated(Announcement[] hwAnnouncements)
483                     throws RemoteException {
484                 List<android.hardware.radio.Announcement> announcements =
485                         new ArrayList<>(hwAnnouncements.length);
486                 for (int i = 0; i < hwAnnouncements.length; i++) {
487                     announcements.add(
488                             ConversionUtils.announcementFromHalAnnouncement(hwAnnouncements[i]));
489                 }
490                 listener.onListUpdated(announcements);
491             }
492         };
493 
494         try {
495             hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList);
496         } catch (RuntimeException ex) {
497             throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener");
498         }
499 
500         return new android.hardware.radio.ICloseHandle.Stub() {
501             public void close() {
502                 try {
503                     hwCloseHandle[0].close();
504                 } catch (RemoteException ex) {
505                     Slogf.e(TAG, ex, "Failed closing announcement listener");
506                 }
507                 hwCloseHandle[0] = null;
508             }
509         };
510     }
511 
512     Bitmap getImage(int id) {
513         mLogger.logRadioEvent("Get image for id = %d", id);
514         if (id == 0) throw new IllegalArgumentException("Image ID is missing");
515 
516         byte[] rawImage;
517         try {
518             rawImage = mService.getImage(id);
519         } catch (RemoteException ex) {
520             throw ex.rethrowFromSystemServer();
521         }
522 
523         if (rawImage == null || rawImage.length == 0) return null;
524 
525         return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
526     }
527 
528     void dumpInfo(IndentingPrintWriter pw) {
529         pw.printf("RadioModule\n");
530 
531         pw.increaseIndent();
532         synchronized (mLock) {
533             pw.printf("BroadcastRadioServiceImpl: %s\n", mService);
534             pw.printf("Properties: %s\n", mProperties);
535             pw.printf("Antenna state: ");
536             if (mAntennaConnected == null) {
537                 pw.printf("undetermined\n");
538             } else {
539                 pw.printf("%s\n", mAntennaConnected ? "connected" : "not connected");
540             }
541             pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo);
542             pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
543             pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters);
544             pw.printf("AIDL TunerSessions [%d]:\n", mAidlTunerSessions.size());
545 
546             pw.increaseIndent();
547             for (int i = 0; i < mAidlTunerSessions.size(); i++) {
548                 mAidlTunerSessions.valueAt(i).dumpInfo(pw);
549             }
550             pw.decreaseIndent();
551         }
552         pw.printf("Radio module events:\n");
553 
554         pw.increaseIndent();
555         mLogger.dump(pw);
556         pw.decreaseIndent();
557 
558         pw.decreaseIndent();
559     }
560 }
561