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