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.IndentingPrintWriter; 42 import android.util.MutableInt; 43 import android.util.Slog; 44 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.annotations.VisibleForTesting; 47 48 import java.util.ArrayList; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Objects; 53 import java.util.Set; 54 import java.util.stream.Collectors; 55 56 class RadioModule { 57 private static final String TAG = "BcRadio2Srv.module"; 58 private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25; 59 60 @NonNull private final IBroadcastRadio mService; 61 @NonNull public final RadioManager.ModuleProperties mProperties; 62 63 private final Object mLock; 64 @NonNull private final Handler mHandler; 65 @NonNull private final RadioEventLogger mEventLogger; 66 67 @GuardedBy("mLock") 68 private ITunerSession mHalTunerSession; 69 70 // Tracks antenna state reported by HAL (if any). 71 @GuardedBy("mLock") 72 private Boolean mAntennaConnected = null; 73 74 @GuardedBy("mLock") 75 private RadioManager.ProgramInfo mCurrentProgramInfo = null; 76 77 @GuardedBy("mLock") 78 private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null); 79 80 @GuardedBy("mLock") 81 private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters = null; 82 83 // Callback registered with the HAL to relay callbacks to AIDL clients. 84 private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() { 85 @Override 86 public void onTuneFailed(int result, ProgramSelector programSelector) { 87 lockAndFireLater(() -> { 88 android.hardware.radio.ProgramSelector csel = 89 Convert.programSelectorFromHal(programSelector); 90 fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel)); 91 }); 92 } 93 94 @Override 95 public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) { 96 lockAndFireLater(() -> { 97 mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo); 98 fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mCurrentProgramInfo)); 99 }); 100 } 101 102 @Override 103 public void onProgramListUpdated(ProgramListChunk programListChunk) { 104 lockAndFireLater(() -> { 105 android.hardware.radio.ProgramList.Chunk chunk = 106 Convert.programListChunkFromHal(programListChunk); 107 mProgramInfoCache.filterAndApplyChunk(chunk); 108 109 for (TunerSession tunerSession : mAidlTunerSessions) { 110 tunerSession.onMergedProgramListUpdateFromHal(chunk); 111 } 112 }); 113 } 114 115 @Override 116 public void onAntennaStateChange(boolean connected) { 117 lockAndFireLater(() -> { 118 mAntennaConnected = connected; 119 fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected)); 120 }); 121 } 122 123 @Override 124 public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) { 125 lockAndFireLater(() -> { 126 Map<String, String> cparam = Convert.vendorInfoFromHal(parameters); 127 fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam)); 128 }); 129 } 130 }; 131 132 // Collection of active AIDL tuner sessions created through openSession(). 133 @GuardedBy("mLock") 134 private final Set<TunerSession> mAidlTunerSessions = new HashSet<>(); 135 136 @VisibleForTesting RadioModule(@onNull IBroadcastRadio service, @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock)137 RadioModule(@NonNull IBroadcastRadio service, 138 @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock) { 139 mProperties = Objects.requireNonNull(properties); 140 mService = Objects.requireNonNull(service); 141 mLock = Objects.requireNonNull(lock); 142 mHandler = new Handler(Looper.getMainLooper()); 143 mEventLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE); 144 } 145 tryLoadingModule(int idx, @NonNull String fqName, Object lock)146 public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName, 147 Object lock) { 148 try { 149 Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName); 150 IBroadcastRadio service = IBroadcastRadio.getService(fqName); 151 if (service == null) { 152 Slog.w(TAG, "No service found for fqName " + fqName); 153 return null; 154 } 155 156 Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>(); 157 service.getAmFmRegionConfig(false, (result, config) -> { 158 if (result == Result.OK) amfmConfig.value = config; 159 }); 160 161 Mutable<List<DabTableEntry>> dabConfig = new Mutable<>(); 162 service.getDabRegionConfig((result, config) -> { 163 if (result == Result.OK) dabConfig.value = config; 164 }); 165 166 RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName, 167 service.getProperties(), amfmConfig.value, dabConfig.value); 168 169 return new RadioModule(service, prop, lock); 170 } catch (RemoteException ex) { 171 Slog.e(TAG, "Failed to load module " + fqName, ex); 172 return null; 173 } 174 } 175 getService()176 public @NonNull IBroadcastRadio getService() { 177 return mService; 178 } 179 openSession(@onNull android.hardware.radio.ITunerCallback userCb)180 public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) 181 throws RemoteException { 182 mEventLogger.logRadioEvent("Open TunerSession"); 183 synchronized (mLock) { 184 if (mHalTunerSession == null) { 185 Mutable<ITunerSession> hwSession = new Mutable<>(); 186 mService.openSession(mHalTunerCallback, (result, session) -> { 187 Convert.throwOnError("openSession", result); 188 hwSession.value = session; 189 mEventLogger.logRadioEvent("New HIDL 2.0 tuner session is opened"); 190 }); 191 mHalTunerSession = Objects.requireNonNull(hwSession.value); 192 } 193 TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb, 194 mLock); 195 mAidlTunerSessions.add(tunerSession); 196 197 // Propagate state to new client. Note: These callbacks are invoked while holding mLock 198 // to prevent race conditions with new callbacks from the HAL. 199 if (mAntennaConnected != null) { 200 userCb.onAntennaState(mAntennaConnected); 201 } 202 if (mCurrentProgramInfo != null) { 203 userCb.onCurrentProgramInfoChanged(mCurrentProgramInfo); 204 } 205 206 return tunerSession; 207 } 208 } 209 closeSessions(Integer error)210 public void closeSessions(Integer error) { 211 // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close() 212 // must be called without mAidlTunerSessions locked because it can call 213 // onTunerSessionClosed(). 214 mEventLogger.logRadioEvent("Close TunerSessions"); 215 TunerSession[] tunerSessions; 216 synchronized (mLock) { 217 tunerSessions = new TunerSession[mAidlTunerSessions.size()]; 218 mAidlTunerSessions.toArray(tunerSessions); 219 mAidlTunerSessions.clear(); 220 } 221 for (TunerSession tunerSession : tunerSessions) { 222 tunerSession.close(error); 223 } 224 } 225 226 private @Nullable android.hardware.radio.ProgramList.Filter buildUnionOfTunerSessionFiltersLocked()227 buildUnionOfTunerSessionFiltersLocked() { 228 Set<Integer> idTypes = null; 229 Set<android.hardware.radio.ProgramSelector.Identifier> ids = null; 230 boolean includeCategories = false; 231 boolean excludeModifications = true; 232 233 for (TunerSession tunerSession : mAidlTunerSessions) { 234 android.hardware.radio.ProgramList.Filter filter = 235 tunerSession.getProgramListFilter(); 236 if (filter == null) { 237 continue; 238 } 239 240 if (idTypes == null) { 241 idTypes = new HashSet<>(filter.getIdentifierTypes()); 242 ids = new HashSet<>(filter.getIdentifiers()); 243 includeCategories = filter.areCategoriesIncluded(); 244 excludeModifications = filter.areModificationsExcluded(); 245 continue; 246 } 247 if (!idTypes.isEmpty()) { 248 if (filter.getIdentifierTypes().isEmpty()) { 249 idTypes.clear(); 250 } else { 251 idTypes.addAll(filter.getIdentifierTypes()); 252 } 253 } 254 255 if (!ids.isEmpty()) { 256 if (filter.getIdentifiers().isEmpty()) { 257 ids.clear(); 258 } else { 259 ids.addAll(filter.getIdentifiers()); 260 } 261 } 262 263 includeCategories |= filter.areCategoriesIncluded(); 264 excludeModifications &= filter.areModificationsExcluded(); 265 } 266 267 return idTypes == null ? null : new android.hardware.radio.ProgramList.Filter(idTypes, ids, 268 includeCategories, excludeModifications); 269 } 270 onTunerSessionProgramListFilterChanged(@ullable TunerSession session)271 void onTunerSessionProgramListFilterChanged(@Nullable TunerSession session) { 272 synchronized (mLock) { 273 onTunerSessionProgramListFilterChangedLocked(session); 274 } 275 } 276 onTunerSessionProgramListFilterChangedLocked(@ullable TunerSession session)277 private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) { 278 android.hardware.radio.ProgramList.Filter newFilter = 279 buildUnionOfTunerSessionFiltersLocked(); 280 if (newFilter == null) { 281 // If there are no AIDL clients remaining, we can stop updates from the HAL as well. 282 if (mUnionOfAidlProgramFilters == null) { 283 return; 284 } 285 mUnionOfAidlProgramFilters = null; 286 try { 287 mHalTunerSession.stopProgramListUpdates(); 288 } catch (RemoteException ex) { 289 Slog.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex); 290 } 291 return; 292 } 293 294 // If the HAL filter doesn't change, we can immediately send an update to the AIDL 295 // client. 296 if (newFilter.equals(mUnionOfAidlProgramFilters)) { 297 if (session != null) { 298 session.updateProgramInfoFromHalCache(mProgramInfoCache); 299 } 300 return; 301 } 302 303 // Otherwise, update the HAL's filter, and AIDL clients will be updated when 304 // mHalTunerCallback.onProgramListUpdated() is called. 305 mUnionOfAidlProgramFilters = newFilter; 306 try { 307 int halResult = mHalTunerSession.startProgramListUpdates(Convert.programFilterToHal( 308 newFilter)); 309 Convert.throwOnError("startProgramListUpdates", halResult); 310 } catch (RemoteException ex) { 311 Slog.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex); 312 } 313 } 314 onTunerSessionClosed(TunerSession tunerSession)315 void onTunerSessionClosed(TunerSession tunerSession) { 316 synchronized (mLock) { 317 onTunerSessionsClosedLocked(tunerSession); 318 } 319 } 320 onTunerSessionsClosedLocked(TunerSession... tunerSessions)321 private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) { 322 for (TunerSession tunerSession : tunerSessions) { 323 mAidlTunerSessions.remove(tunerSession); 324 } 325 onTunerSessionProgramListFilterChanged(null); 326 if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) { 327 mEventLogger.logRadioEvent("Closing HAL tuner session"); 328 try { 329 mHalTunerSession.close(); 330 } catch (RemoteException ex) { 331 Slog.e(TAG, "mHalTunerSession.close() failed: ", ex); 332 } 333 mHalTunerSession = null; 334 } 335 } 336 337 // add to mHandler queue, but ensure the runnable holds mLock when it gets executed lockAndFireLater(Runnable r)338 private void lockAndFireLater(Runnable r) { 339 mHandler.post(() -> { 340 synchronized (mLock) { 341 r.run(); 342 } 343 }); 344 } 345 346 interface AidlCallbackRunnable { run(android.hardware.radio.ITunerCallback callback)347 void run(android.hardware.radio.ITunerCallback callback) throws RemoteException; 348 } 349 350 // Invokes runnable with each TunerSession currently open. fanoutAidlCallback(AidlCallbackRunnable runnable)351 void fanoutAidlCallback(AidlCallbackRunnable runnable) { 352 lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable)); 353 } 354 fanoutAidlCallbackLocked(AidlCallbackRunnable runnable)355 private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) { 356 List<TunerSession> deadSessions = null; 357 for (TunerSession tunerSession : mAidlTunerSessions) { 358 try { 359 runnable.run(tunerSession.mCallback); 360 } catch (DeadObjectException ex) { 361 // The other side died without calling close(), so just purge it from our records. 362 Slog.e(TAG, "Removing dead TunerSession"); 363 if (deadSessions == null) { 364 deadSessions = new ArrayList<>(); 365 } 366 deadSessions.add(tunerSession); 367 } catch (RemoteException ex) { 368 Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex); 369 } 370 } 371 if (deadSessions != null) { 372 onTunerSessionsClosedLocked(deadSessions.toArray( 373 new TunerSession[deadSessions.size()])); 374 } 375 } 376 addAnnouncementListener(@onNull int[] enabledTypes, @NonNull android.hardware.radio.IAnnouncementListener listener)377 public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes, 378 @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException { 379 mEventLogger.logRadioEvent("Add AnnouncementListener"); 380 ArrayList<Byte> enabledList = new ArrayList<>(); 381 for (int type : enabledTypes) { 382 enabledList.add((byte)type); 383 } 384 385 MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); 386 Mutable<ICloseHandle> hwCloseHandle = new Mutable<>(); 387 IAnnouncementListener hwListener = new IAnnouncementListener.Stub() { 388 public void onListUpdated(ArrayList<Announcement> hwAnnouncements) 389 throws RemoteException { 390 listener.onListUpdated(hwAnnouncements.stream(). 391 map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList())); 392 } 393 }; 394 395 synchronized (mLock) { 396 mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> { 397 halResult.value = result; 398 hwCloseHandle.value = closeHnd; 399 }); 400 } 401 Convert.throwOnError("addAnnouncementListener", halResult.value); 402 403 return new android.hardware.radio.ICloseHandle.Stub() { 404 public void close() { 405 try { 406 hwCloseHandle.value.close(); 407 } catch (RemoteException ex) { 408 Slog.e(TAG, "Failed closing announcement listener", ex); 409 } 410 hwCloseHandle.value = null; 411 } 412 }; 413 } 414 415 Bitmap getImage(int id) { 416 mEventLogger.logRadioEvent("Get image for id %d", id); 417 if (id == 0) throw new IllegalArgumentException("Image ID is missing"); 418 419 byte[] rawImage; 420 synchronized (mLock) { 421 List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id)); 422 rawImage = new byte[rawList.size()]; 423 for (int i = 0; i < rawList.size(); i++) { 424 rawImage[i] = rawList.get(i); 425 } 426 } 427 428 if (rawImage == null || rawImage.length == 0) return null; 429 430 return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length); 431 } 432 433 void dumpInfo(IndentingPrintWriter pw) { 434 pw.printf("RadioModule\n"); 435 pw.increaseIndent(); 436 synchronized (mLock) { 437 pw.printf("BroadcastRadioService: %s\n", mService); 438 pw.printf("Properties: %s\n", mProperties); 439 pw.printf("HIDL2.0 HAL TunerSession: %s\n", mHalTunerSession); 440 pw.printf("Is antenna connected? "); 441 if (mAntennaConnected == null) { 442 pw.printf("null\n"); 443 } else { 444 pw.printf("%s\n", mAntennaConnected ? "Yes" : "No"); 445 } 446 pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo); 447 pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache); 448 pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters); 449 pw.printf("AIDL TunerSessions:\n"); 450 pw.increaseIndent(); 451 for (TunerSession aidlTunerSession : mAidlTunerSessions) { 452 aidlTunerSession.dumpInfo(pw); 453 } 454 pw.decreaseIndent(); 455 } 456 pw.printf("Radio module events:\n"); 457 pw.increaseIndent(); 458 mEventLogger.dump(pw); 459 pw.decreaseIndent(); 460 pw.decreaseIndent(); 461 } 462 } 463