• 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.RemoteException;
39 import android.util.MutableInt;
40 import android.util.Slog;
41 
42 import com.android.internal.annotations.GuardedBy;
43 
44 import java.util.ArrayList;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Objects;
48 import java.util.Set;
49 import java.util.stream.Collectors;
50 
51 class RadioModule {
52     private static final String TAG = "BcRadio2Srv.module";
53 
54     @NonNull private final IBroadcastRadio mService;
55     @NonNull public final RadioManager.ModuleProperties mProperties;
56 
57     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 
RadioModule(@onNull IBroadcastRadio service, @NonNull RadioManager.ModuleProperties properties)112     private RadioModule(@NonNull IBroadcastRadio service,
113             @NonNull RadioManager.ModuleProperties properties) throws RemoteException {
114         mProperties = Objects.requireNonNull(properties);
115         mService = Objects.requireNonNull(service);
116     }
117 
tryLoadingModule(int idx, @NonNull String fqName)118     public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
119         try {
120             IBroadcastRadio service = IBroadcastRadio.getService(fqName);
121             if (service == null) return null;
122 
123             Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
124             service.getAmFmRegionConfig(false, (result, config) -> {
125                 if (result == Result.OK) amfmConfig.value = config;
126             });
127 
128             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);
135 
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     }
142 
getService()143     public @NonNull IBroadcastRadio getService() {
144         return mService;
145     }
146 
openSession(@onNull android.hardware.radio.ITunerCallback userCb)147     public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
148             throws RemoteException {
149         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);
160 
161             // 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;
171         }
172     }
173 
closeSessions(Integer error)174     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     }
188 
onTunerSessionClosed(TunerSession tunerSession)189     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 {
run(android.hardware.radio.ITunerCallback callback)205         void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
206     }
207 
208     // Invokes runnable with each TunerSession currently open.
fanoutAidlCallback(AidlCallbackRunnable runnable)209     void fanoutAidlCallback(AidlCallbackRunnable runnable) {
210         synchronized (mLock) {
211             fanoutAidlCallbackLocked(runnable);
212         }
213     }
214 
fanoutAidlCallbackLocked(AidlCallbackRunnable runnable)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         }
228     }
229 
addAnnouncementListener(@onNull int[] enabledTypes, @NonNull android.hardware.radio.IAnnouncementListener listener)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         };
246 
247         synchronized (mService) {
248             mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
249                 halResult.value = result;
250                 hwCloseHandle.value = closeHnd;
251             });
252         }
253         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     }
265 
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     }
282 }
283