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 
46 import java.util.ArrayList;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Objects;
51 import java.util.Set;
52 import java.util.stream.Collectors;
53 
54 class RadioModule {
55     private static final String TAG = "BcRadio2Srv.module";
56 
57     @NonNull private final IBroadcastRadio mService;
58     @NonNull public final RadioManager.ModuleProperties mProperties;
59 
60     private final Object mLock = new Object();
61     @NonNull private final Handler mHandler;
62 
63     @GuardedBy("mLock")
64     private ITunerSession mHalTunerSession;
65 
66     // Tracks antenna state reported by HAL (if any).
67     @GuardedBy("mLock")
68     private Boolean mAntennaConnected = null;
69 
70     @GuardedBy("mLock")
71     private RadioManager.ProgramInfo mProgramInfo = null;
72 
73     // Callback registered with the HAL to relay callbacks to AIDL clients.
74     private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
75         @Override
76         public void onTuneFailed(int result, ProgramSelector programSelector) {
77             lockAndFireLater(() -> {
78                 android.hardware.radio.ProgramSelector csel =
79                         Convert.programSelectorFromHal(programSelector);
80                 fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
81             });
82         }
83 
84         @Override
85         public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
86             lockAndFireLater(() -> {
87                 mProgramInfo = Convert.programInfoFromHal(halProgramInfo);
88                 fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mProgramInfo));
89             });
90         }
91 
92         @Override
93         public void onProgramListUpdated(ProgramListChunk programListChunk) {
94             // TODO: Cache per-AIDL client filters, send union of filters to HAL, use filters to fan
95             // back out to clients.
96             lockAndFireLater(() -> {
97                 android.hardware.radio.ProgramList.Chunk chunk =
98                         Convert.programListChunkFromHal(programListChunk);
99                 fanoutAidlCallbackLocked(cb -> cb.onProgramListUpdated(chunk));
100             });
101         }
102 
103         @Override
104         public void onAntennaStateChange(boolean connected) {
105             lockAndFireLater(() -> {
106                 mAntennaConnected = connected;
107                 fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
108             });
109         }
110 
111         @Override
112         public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
113             lockAndFireLater(() -> {
114                 Map<String, String> cparam = Convert.vendorInfoFromHal(parameters);
115                 fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
116             });
117         }
118     };
119 
120     // Collection of active AIDL tuner sessions created through openSession().
121     @GuardedBy("mLock")
122     private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();
123 
RadioModule(@onNull IBroadcastRadio service, @NonNull RadioManager.ModuleProperties properties)124     private RadioModule(@NonNull IBroadcastRadio service,
125             @NonNull RadioManager.ModuleProperties properties) throws RemoteException {
126         mProperties = Objects.requireNonNull(properties);
127         mService = Objects.requireNonNull(service);
128         mHandler = new Handler(Looper.getMainLooper());
129     }
130 
tryLoadingModule(int idx, @NonNull String fqName)131     public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
132         try {
133             IBroadcastRadio service = IBroadcastRadio.getService(fqName);
134             if (service == null) return null;
135 
136             Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
137             service.getAmFmRegionConfig(false, (result, config) -> {
138                 if (result == Result.OK) amfmConfig.value = config;
139             });
140 
141             Mutable<List<DabTableEntry>> dabConfig = new Mutable<>();
142             service.getDabRegionConfig((result, config) -> {
143                 if (result == Result.OK) dabConfig.value = config;
144             });
145 
146             RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName,
147                     service.getProperties(), amfmConfig.value, dabConfig.value);
148 
149             return new RadioModule(service, prop);
150         } catch (RemoteException ex) {
151             Slog.e(TAG, "failed to load module " + fqName, ex);
152             return null;
153         }
154     }
155 
getService()156     public @NonNull IBroadcastRadio getService() {
157         return mService;
158     }
159 
openSession(@onNull android.hardware.radio.ITunerCallback userCb)160     public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
161             throws RemoteException {
162         synchronized (mLock) {
163             if (mHalTunerSession == null) {
164                 Mutable<ITunerSession> hwSession = new Mutable<>();
165                 mService.openSession(mHalTunerCallback, (result, session) -> {
166                     Convert.throwOnError("openSession", result);
167                     hwSession.value = session;
168                 });
169                 mHalTunerSession = Objects.requireNonNull(hwSession.value);
170             }
171             TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb);
172             mAidlTunerSessions.add(tunerSession);
173 
174             // Propagate state to new client. Note: These callbacks are invoked while holding mLock
175             // to prevent race conditions with new callbacks from the HAL.
176             if (mAntennaConnected != null) {
177                 userCb.onAntennaState(mAntennaConnected);
178             }
179             if (mProgramInfo != null) {
180                 userCb.onCurrentProgramInfoChanged(mProgramInfo);
181             }
182 
183             return tunerSession;
184         }
185     }
186 
closeSessions(Integer error)187     public void closeSessions(Integer error) {
188         // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
189         // must be called without mAidlTunerSessions locked because it can call
190         // onTunerSessionClosed().
191         TunerSession[] tunerSessions;
192         synchronized (mLock) {
193             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
194             mAidlTunerSessions.toArray(tunerSessions);
195             mAidlTunerSessions.clear();
196         }
197         for (TunerSession tunerSession : tunerSessions) {
198             tunerSession.close(error);
199         }
200     }
201 
onTunerSessionClosed(TunerSession tunerSession)202     void onTunerSessionClosed(TunerSession tunerSession) {
203         synchronized (mLock) {
204             mAidlTunerSessions.remove(tunerSession);
205             if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
206                 Slog.v(TAG, "closing HAL tuner session");
207                 try {
208                     mHalTunerSession.close();
209                 } catch (RemoteException ex) {
210                     Slog.e(TAG, "mHalTunerSession.close() failed: ", ex);
211                 }
212                 mHalTunerSession = null;
213             }
214         }
215     }
216 
217     // add to mHandler queue, but ensure the runnable holds mLock when it gets executed
lockAndFireLater(Runnable r)218     private void lockAndFireLater(Runnable r) {
219         mHandler.post(() -> {
220             synchronized (mLock) {
221                 r.run();
222             }
223         });
224     }
225 
226     interface AidlCallbackRunnable {
run(android.hardware.radio.ITunerCallback callback)227         void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
228     }
229 
230     // Invokes runnable with each TunerSession currently open.
fanoutAidlCallback(AidlCallbackRunnable runnable)231     void fanoutAidlCallback(AidlCallbackRunnable runnable) {
232         lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable));
233     }
234 
fanoutAidlCallbackLocked(AidlCallbackRunnable runnable)235     private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
236         List<TunerSession> deadSessions = null;
237         for (TunerSession tunerSession : mAidlTunerSessions) {
238             try {
239                 runnable.run(tunerSession.mCallback);
240             } catch (DeadObjectException ex) {
241                 // The other side died without calling close(), so just purge it from our records.
242                 Slog.e(TAG, "Removing dead TunerSession");
243                 if (deadSessions == null) {
244                     deadSessions = new ArrayList<>();
245                 }
246                 deadSessions.add(tunerSession);
247             } catch (RemoteException ex) {
248                 Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
249             }
250         }
251         if (deadSessions != null) {
252             mAidlTunerSessions.removeAll(deadSessions);
253         }
254     }
255 
addAnnouncementListener(@onNull int[] enabledTypes, @NonNull android.hardware.radio.IAnnouncementListener listener)256     public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
257             @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
258         ArrayList<Byte> enabledList = new ArrayList<>();
259         for (int type : enabledTypes) {
260             enabledList.add((byte)type);
261         }
262 
263         MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
264         Mutable<ICloseHandle> hwCloseHandle = new Mutable<>();
265         IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
266             public void onListUpdated(ArrayList<Announcement> hwAnnouncements)
267                     throws RemoteException {
268                 listener.onListUpdated(hwAnnouncements.stream().
269                     map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
270             }
271         };
272 
273         synchronized (mService) {
274             mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
275                 halResult.value = result;
276                 hwCloseHandle.value = closeHnd;
277             });
278         }
279         Convert.throwOnError("addAnnouncementListener", halResult.value);
280 
281         return new android.hardware.radio.ICloseHandle.Stub() {
282             public void close() {
283                 try {
284                     hwCloseHandle.value.close();
285                 } catch (RemoteException ex) {
286                     Slog.e(TAG, "Failed closing announcement listener", ex);
287                 }
288             }
289         };
290     }
291 
292     Bitmap getImage(int id) {
293         if (id == 0) throw new IllegalArgumentException("Image ID is missing");
294 
295         byte[] rawImage;
296         synchronized (mService) {
297             List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
298             rawImage = new byte[rawList.size()];
299             for (int i = 0; i < rawList.size(); i++) {
300                 rawImage[i] = rawList.get(i);
301             }
302         }
303 
304         if (rawImage == null || rawImage.length == 0) return null;
305 
306         return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
307     }
308 }
309