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