blob: acb0207ff11f23327f9ac12c31b0e7b388c8512e [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 Wasilczykd65b3ca2017-12-13 08:26:25 -080038import android.os.RemoteException;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080039import android.util.MutableInt;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080040import android.util.Slog;
41
George Lub45cdc72019-03-29 16:53:51 -070042import com.android.internal.annotations.GuardedBy;
43
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080044import java.util.ArrayList;
George Lub45cdc72019-03-29 16:53:51 -070045import java.util.HashSet;
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080046import java.util.List;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080047import java.util.Objects;
George Lub45cdc72019-03-29 16:53:51 -070048import java.util.Set;
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -080049import java.util.stream.Collectors;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080050
51class RadioModule {
52 private static final String TAG = "BcRadio2Srv.module";
53
54 @NonNull private final IBroadcastRadio mService;
55 @NonNull public final RadioManager.ModuleProperties mProperties;
56
George Lub45cdc72019-03-29 16:53:51 -070057 private final Object mLock = new Object();
58
59 @GuardedBy("mLock")
60 private ITunerSession mHalTunerSession;
61
62 // Tracks antenna state reported by HAL (if any).
63 @GuardedBy("mLock")
64 private Boolean mAntennaConnected = null;
65
66 @GuardedBy("mLock")
67 private RadioManager.ProgramInfo mProgramInfo = null;
68
69 // Callback registered with the HAL to relay callbacks to AIDL clients.
70 private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
71 @Override
72 public void onTuneFailed(int result, ProgramSelector programSelector) {
73 fanoutAidlCallback(cb -> cb.onTuneFailed(result, Convert.programSelectorFromHal(
74 programSelector)));
75 }
76
77 @Override
78 public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
79 RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo);
80 synchronized (mLock) {
81 mProgramInfo = programInfo;
82 fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(programInfo));
83 }
84 }
85
86 @Override
87 public void onProgramListUpdated(ProgramListChunk programListChunk) {
88 // TODO: Cache per-AIDL client filters, send union of filters to HAL, use filters to fan
89 // back out to clients.
90 fanoutAidlCallback(cb -> cb.onProgramListUpdated(Convert.programListChunkFromHal(
91 programListChunk)));
92 }
93
94 @Override
95 public void onAntennaStateChange(boolean connected) {
96 synchronized (mLock) {
97 mAntennaConnected = connected;
98 fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
99 }
100 }
101
102 @Override
103 public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
104 fanoutAidlCallback(cb -> cb.onParametersUpdated(Convert.vendorInfoFromHal(parameters)));
105 }
106 };
107
108 // Collection of active AIDL tuner sessions created through openSession().
109 @GuardedBy("mLock")
110 private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();
111
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800112 private RadioModule(@NonNull IBroadcastRadio service,
George Lub45cdc72019-03-29 16:53:51 -0700113 @NonNull RadioManager.ModuleProperties properties) throws RemoteException {
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800114 mProperties = Objects.requireNonNull(properties);
115 mService = Objects.requireNonNull(service);
116 }
117
118 public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
119 try {
Tomasz Wasilczykd0ac0bc2018-03-01 08:21:55 -0800120 IBroadcastRadio service = IBroadcastRadio.getService(fqName);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800121 if (service == null) return null;
122
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800123 Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
Tomasz Wasilczyk58f34062018-01-13 08:25:17 -0800124 service.getAmFmRegionConfig(false, (result, config) -> {
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800125 if (result == Result.OK) amfmConfig.value = config;
126 });
127
Tomasz Wasilczyk58f34062018-01-13 08:25:17 -0800128 Mutable<List<DabTableEntry>> dabConfig = new Mutable<>();
129 service.getDabRegionConfig((result, config) -> {
130 if (result == Result.OK) dabConfig.value = config;
131 });
132
133 RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName,
134 service.getProperties(), amfmConfig.value, dabConfig.value);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800135
136 return new RadioModule(service, prop);
137 } catch (RemoteException ex) {
138 Slog.e(TAG, "failed to load module " + fqName, ex);
139 return null;
140 }
141 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800142
George Lu5af724322019-03-13 17:11:21 -0700143 public @NonNull IBroadcastRadio getService() {
144 return mService;
145 }
146
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800147 public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
148 throws RemoteException {
George Lub45cdc72019-03-29 16:53:51 -0700149 synchronized (mLock) {
150 if (mHalTunerSession == null) {
151 Mutable<ITunerSession> hwSession = new Mutable<>();
152 mService.openSession(mHalTunerCallback, (result, session) -> {
153 Convert.throwOnError("openSession", result);
154 hwSession.value = session;
155 });
156 mHalTunerSession = Objects.requireNonNull(hwSession.value);
157 }
158 TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb);
159 mAidlTunerSessions.add(tunerSession);
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800160
George Lub45cdc72019-03-29 16:53:51 -0700161 // Propagate state to new client. Note: These callbacks are invoked while holding mLock
162 // to prevent race conditions with new callbacks from the HAL.
163 if (mAntennaConnected != null) {
164 userCb.onAntennaState(mAntennaConnected);
165 }
166 if (mProgramInfo != null) {
167 userCb.onCurrentProgramInfoChanged(mProgramInfo);
168 }
169
170 return tunerSession;
Tomasz Wasilczyk3b4465e2018-01-14 21:47:44 -0800171 }
George Lub45cdc72019-03-29 16:53:51 -0700172 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800173
George Lub45cdc72019-03-29 16:53:51 -0700174 public void closeSessions(Integer error) {
175 // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
176 // must be called without mAidlTunerSessions locked because it can call
177 // onTunerSessionClosed().
178 TunerSession[] tunerSessions;
179 synchronized (mLock) {
180 tunerSessions = new TunerSession[mAidlTunerSessions.size()];
181 mAidlTunerSessions.toArray(tunerSessions);
182 mAidlTunerSessions.clear();
183 }
184 for (TunerSession tunerSession : tunerSessions) {
185 tunerSession.close(error);
186 }
187 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800188
George Lub45cdc72019-03-29 16:53:51 -0700189 void onTunerSessionClosed(TunerSession tunerSession) {
190 synchronized (mLock) {
191 mAidlTunerSessions.remove(tunerSession);
192 if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
193 Slog.v(TAG, "closing HAL tuner session");
194 try {
195 mHalTunerSession.close();
196 } catch (RemoteException ex) {
197 Slog.e(TAG, "mHalTunerSession.close() failed: ", ex);
198 }
199 mHalTunerSession = null;
200 }
201 }
202 }
203
204 interface AidlCallbackRunnable {
205 void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
206 }
207
208 // Invokes runnable with each TunerSession currently open.
209 void fanoutAidlCallback(AidlCallbackRunnable runnable) {
210 synchronized (mLock) {
211 fanoutAidlCallbackLocked(runnable);
212 }
213 }
214
215 private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
216 for (TunerSession tunerSession : mAidlTunerSessions) {
217 try {
218 runnable.run(tunerSession.mCallback);
219 } catch (DeadObjectException ex) {
220 // The other side died without calling close(), so just purge it from our
221 // records.
222 Slog.e(TAG, "Removing dead TunerSession");
223 mAidlTunerSessions.remove(tunerSession);
224 } catch (RemoteException ex) {
225 Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
226 }
227 }
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800228 }
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -0800229
230 public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
231 @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
232 ArrayList<Byte> enabledList = new ArrayList<>();
233 for (int type : enabledTypes) {
234 enabledList.add((byte)type);
235 }
236
237 MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
238 Mutable<ICloseHandle> hwCloseHandle = new Mutable<>();
239 IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
240 public void onListUpdated(ArrayList<Announcement> hwAnnouncements)
241 throws RemoteException {
242 listener.onListUpdated(hwAnnouncements.stream().
243 map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
244 }
245 };
Tomasz Wasilczyk3b4465e2018-01-14 21:47:44 -0800246
247 synchronized (mService) {
248 mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
249 halResult.value = result;
250 hwCloseHandle.value = closeHnd;
251 });
252 }
Tomasz Wasilczykf151a7b2018-01-11 16:03:46 -0800253 Convert.throwOnError("addAnnouncementListener", halResult.value);
254
255 return new android.hardware.radio.ICloseHandle.Stub() {
256 public void close() {
257 try {
258 hwCloseHandle.value.close();
259 } catch (RemoteException ex) {
260 Slog.e(TAG, "Failed closing announcement listener", ex);
261 }
262 }
263 };
264 }
Tomasz Wasilczyk3b4465e2018-01-14 21:47:44 -0800265
266 Bitmap getImage(int id) {
267 if (id == 0) throw new IllegalArgumentException("Image ID is missing");
268
269 byte[] rawImage;
270 synchronized (mService) {
271 List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
272 rawImage = new byte[rawList.size()];
273 for (int i = 0; i < rawList.size(); i++) {
274 rawImage[i] = rawList.get(i);
275 }
276 }
277
278 if (rawImage == null || rawImage.length == 0) return null;
279
280 return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
281 }
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800282}