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