blob: 85ca627e3b02131c68232fd5e7ae544db06ff943 [file] [log] [blame]
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -08001/**
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
17package com.android.server.broadcastradio.hal2;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
Tomasz Wasilczyk3b4465e2018-01-14 21:47:44 -080021import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080023import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080024import android.hardware.broadcastradio.V2_0.Announcement;
Tomasz Wasilczyk58f34062018-01-13 08:25:17 -080025import android.hardware.broadcastradio.V2_0.DabTableEntry;
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080026import android.hardware.broadcastradio.V2_0.IAnnouncementListener;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080027import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080028import android.hardware.broadcastradio.V2_0.ICloseHandle;
George Lub45cdc72019-03-29 16:53:51 -070029import android.hardware.broadcastradio.V2_0.ITunerCallback;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080030import android.hardware.broadcastradio.V2_0.ITunerSession;
George Lub45cdc72019-03-29 16:53:51 -070031import android.hardware.broadcastradio.V2_0.ProgramInfo;
32import android.hardware.broadcastradio.V2_0.ProgramListChunk;
33import android.hardware.broadcastradio.V2_0.ProgramSelector;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080034import android.hardware.broadcastradio.V2_0.Result;
George Lub45cdc72019-03-29 16:53:51 -070035import android.hardware.broadcastradio.V2_0.VendorKeyValue;
George Lu5af724322019-03-13 17:11:21 -070036import android.hardware.radio.RadioManager;
George Lub45cdc72019-03-29 16:53:51 -070037import android.os.DeadObjectException;
Tomasz Wasilczyk82918752019-08-15 14:50:31 -070038import android.os.Handler;
39import android.os.Looper;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080040import android.os.RemoteException;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080041import android.util.MutableInt;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080042import android.util.Slog;
43
George Lub45cdc72019-03-29 16:53:51 -070044import com.android.internal.annotations.GuardedBy;
45
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080046import java.util.ArrayList;
George Lub45cdc72019-03-29 16:53:51 -070047import java.util.HashSet;
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080048import java.util.List;
Tomasz Wasilczyk82918752019-08-15 14:50:31 -070049import java.util.Map;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080050import java.util.Objects;
George Lub45cdc72019-03-29 16:53:51 -070051import java.util.Set;
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080052import java.util.stream.Collectors;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080053
54class RadioModule {
55 private static final String TAG = "BcRadio2Srv.module";
56
57 @NonNull private final IBroadcastRadio mService;
58 @NonNull public final RadioManager.ModuleProperties mProperties;
59
George Lub45cdc72019-03-29 16:53:51 -070060 private final Object mLock = new Object();
Tomasz Wasilczyk82918752019-08-15 14:50:31 -070061 @NonNull private final Handler mHandler;
George Lub45cdc72019-03-29 16:53:51 -070062
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) {
Tomasz Wasilczyk82918752019-08-15 14:50:31 -070077 lockAndFireLater(() -> {
78 android.hardware.radio.ProgramSelector csel =
79 Convert.programSelectorFromHal(programSelector);
80 fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
81 });
George Lub45cdc72019-03-29 16:53:51 -070082 }
83
84 @Override
85 public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
Tomasz Wasilczyk82918752019-08-15 14:50:31 -070086 lockAndFireLater(() -> {
87 mProgramInfo = Convert.programInfoFromHal(halProgramInfo);
88 fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mProgramInfo));
89 });
George Lub45cdc72019-03-29 16:53:51 -070090 }
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.
Tomasz Wasilczyk82918752019-08-15 14:50:31 -070096 lockAndFireLater(() -> {
97 android.hardware.radio.ProgramList.Chunk chunk =
98 Convert.programListChunkFromHal(programListChunk);
99 fanoutAidlCallbackLocked(cb -> cb.onProgramListUpdated(chunk));
100 });
George Lub45cdc72019-03-29 16:53:51 -0700101 }
102
103 @Override
104 public void onAntennaStateChange(boolean connected) {
Tomasz Wasilczyk82918752019-08-15 14:50:31 -0700105 lockAndFireLater(() -> {
George Lub45cdc72019-03-29 16:53:51 -0700106 mAntennaConnected = connected;
107 fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
Tomasz Wasilczyk82918752019-08-15 14:50:31 -0700108 });
George Lub45cdc72019-03-29 16:53:51 -0700109 }
110
111 @Override
112 public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
Tomasz Wasilczyk82918752019-08-15 14:50:31 -0700113 lockAndFireLater(() -> {
114 Map<String, String> cparam = Convert.vendorInfoFromHal(parameters);
115 fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
116 });
George Lub45cdc72019-03-29 16:53:51 -0700117 }
118 };
119
120 // Collection of active AIDL tuner sessions created through openSession().
121 @GuardedBy("mLock")
122 private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();
123
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800124 private RadioModule(@NonNull IBroadcastRadio service,
George Lub45cdc72019-03-29 16:53:51 -0700125 @NonNull RadioManager.ModuleProperties properties) throws RemoteException {
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800126 mProperties = Objects.requireNonNull(properties);
127 mService = Objects.requireNonNull(service);
Tomasz Wasilczyk82918752019-08-15 14:50:31 -0700128 mHandler = new Handler(Looper.getMainLooper());
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800129 }
130
131 public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
132 try {
Tomasz Wasilczykd0ac0bc2018-03-01 08:21:55 -0800133 IBroadcastRadio service = IBroadcastRadio.getService(fqName);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800134 if (service == null) return null;
135
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800136 Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
Tomasz Wasilczyk58f34062018-01-13 08:25:17 -0800137 service.getAmFmRegionConfig(false, (result, config) -> {
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800138 if (result == Result.OK) amfmConfig.value = config;
139 });
140
Tomasz Wasilczyk58f34062018-01-13 08:25:17 -0800141 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);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800148
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 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800155
George Lu5af724322019-03-13 17:11:21 -0700156 public @NonNull IBroadcastRadio getService() {
157 return mService;
158 }
159
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800160 public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
161 throws RemoteException {
George Lub45cdc72019-03-29 16:53:51 -0700162 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);
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800173
George Lub45cdc72019-03-29 16:53:51 -0700174 // 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;
Tomasz Wasilczyk3b4465e2018-01-14 21:47:44 -0800184 }
George Lub45cdc72019-03-29 16:53:51 -0700185 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800186
George Lub45cdc72019-03-29 16:53:51 -0700187 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 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800201
George Lub45cdc72019-03-29 16:53:51 -0700202 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
Tomasz Wasilczyk82918752019-08-15 14:50:31 -0700217 // add to mHandler queue, but ensure the runnable holds mLock when it gets executed
218 private void lockAndFireLater(Runnable r) {
219 mHandler.post(() -> {
220 synchronized (mLock) {
221 r.run();
222 }
223 });
224 }
225
George Lub45cdc72019-03-29 16:53:51 -0700226 interface AidlCallbackRunnable {
227 void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
228 }
229
230 // Invokes runnable with each TunerSession currently open.
231 void fanoutAidlCallback(AidlCallbackRunnable runnable) {
Tomasz Wasilczyk82918752019-08-15 14:50:31 -0700232 lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable));
George Lub45cdc72019-03-29 16:53:51 -0700233 }
234
235 private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
236 for (TunerSession tunerSession : mAidlTunerSessions) {
237 try {
238 runnable.run(tunerSession.mCallback);
239 } catch (DeadObjectException ex) {
240 // The other side died without calling close(), so just purge it from our
241 // records.
242 Slog.e(TAG, "Removing dead TunerSession");
243 mAidlTunerSessions.remove(tunerSession);
244 } catch (RemoteException ex) {
245 Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
246 }
247 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800248 }
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -0800249
250 public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
251 @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
252 ArrayList<Byte> enabledList = new ArrayList<>();
253 for (int type : enabledTypes) {
254 enabledList.add((byte)type);
255 }
256
257 MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
258 Mutable<ICloseHandle> hwCloseHandle = new Mutable<>();
259 IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
260 public void onListUpdated(ArrayList<Announcement> hwAnnouncements)
261 throws RemoteException {
262 listener.onListUpdated(hwAnnouncements.stream().
263 map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
264 }
265 };
Tomasz Wasilczyk3b4465e2018-01-14 21:47:44 -0800266
267 synchronized (mService) {
268 mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
269 halResult.value = result;
270 hwCloseHandle.value = closeHnd;
271 });
272 }
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -0800273 Convert.throwOnError("addAnnouncementListener", halResult.value);
274
275 return new android.hardware.radio.ICloseHandle.Stub() {
276 public void close() {
277 try {
278 hwCloseHandle.value.close();
279 } catch (RemoteException ex) {
280 Slog.e(TAG, "Failed closing announcement listener", ex);
281 }
282 }
283 };
284 }
Tomasz Wasilczyk3b4465e2018-01-14 21:47:44 -0800285
286 Bitmap getImage(int id) {
287 if (id == 0) throw new IllegalArgumentException("Image ID is missing");
288
289 byte[] rawImage;
290 synchronized (mService) {
291 List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
292 rawImage = new byte[rawList.size()];
293 for (int i = 0; i < rawList.size(); i++) {
294 rawImage[i] = rawList.get(i);
295 }
296 }
297
298 if (rawImage == null || rawImage.length == 0) return null;
299
300 return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
301 }
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800302}