1 /* 2 * Copyright (C) 2022 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.aidl; 18 19 import android.annotation.Nullable; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.hardware.broadcastradio.AmFmRegionConfig; 23 import android.hardware.broadcastradio.Announcement; 24 import android.hardware.broadcastradio.DabTableEntry; 25 import android.hardware.broadcastradio.IAnnouncementListener; 26 import android.hardware.broadcastradio.IBroadcastRadio; 27 import android.hardware.broadcastradio.ICloseHandle; 28 import android.hardware.broadcastradio.ITunerCallback; 29 import android.hardware.broadcastradio.ProgramInfo; 30 import android.hardware.broadcastradio.ProgramListChunk; 31 import android.hardware.broadcastradio.ProgramSelector; 32 import android.hardware.broadcastradio.VendorKeyValue; 33 import android.hardware.radio.RadioManager; 34 import android.os.DeadObjectException; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.RemoteException; 39 import android.os.UserHandle; 40 import android.util.ArraySet; 41 import android.util.IndentingPrintWriter; 42 43 import com.android.internal.annotations.GuardedBy; 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.server.broadcastradio.RadioEventLogger; 46 import com.android.server.broadcastradio.RadioServiceUserController; 47 import com.android.server.utils.Slogf; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Objects; 53 import java.util.Set; 54 55 final class RadioModule { 56 private static final String TAG = "BcRadioAidlSrv.module"; 57 private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25; 58 59 private final IBroadcastRadio mService; 60 61 private final Object mLock = new Object(); 62 private final Handler mHandler; 63 private final RadioEventLogger mLogger; 64 private final RadioManager.ModuleProperties mProperties; 65 66 /** 67 * Tracks antenna state reported by HAL (if any). 68 */ 69 @GuardedBy("mLock") 70 private Boolean mAntennaConnected; 71 72 @GuardedBy("mLock") 73 private RadioManager.ProgramInfo mCurrentProgramInfo; 74 75 @GuardedBy("mLock") 76 private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null); 77 78 @GuardedBy("mLock") 79 private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters; 80 81 /** 82 * Set of active AIDL tuner sessions created through openSession(). 83 */ 84 @GuardedBy("mLock") 85 private final ArraySet<TunerSession> mAidlTunerSessions = new ArraySet<>(); 86 87 /** 88 * Callback registered with the HAL to relay callbacks to AIDL clients. 89 */ 90 private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() { 91 @Override 92 public int getInterfaceVersion() { 93 return this.VERSION; 94 } 95 96 @Override 97 public String getInterfaceHash() { 98 return this.HASH; 99 } 100 101 public void onTuneFailed(int result, ProgramSelector programSelector) { 102 fireLater(() -> { 103 android.hardware.radio.ProgramSelector csel = 104 ConversionUtils.programSelectorFromHalProgramSelector(programSelector); 105 int tunerResult = ConversionUtils.halResultToTunerResult(result); 106 synchronized (mLock) { 107 fanoutAidlCallbackLocked((cb, uid) -> { 108 if (csel != null && !ConversionUtils 109 .programSelectorMeetsSdkVersionRequirement(csel, uid)) { 110 Slogf.e(TAG, "onTuneFailed: cannot send program selector " 111 + "requiring higher target SDK version"); 112 return; 113 } 114 cb.onTuneFailed(tunerResult, csel); 115 }); 116 } 117 }); 118 } 119 120 @Override 121 public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) { 122 fireLater(() -> { 123 RadioManager.ProgramInfo currentProgramInfo = 124 ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo); 125 Objects.requireNonNull(currentProgramInfo, 126 "Program info from AIDL HAL is invalid"); 127 synchronized (mLock) { 128 mCurrentProgramInfo = currentProgramInfo; 129 fanoutAidlCallbackLocked((cb, uid) -> { 130 if (!ConversionUtils.programInfoMeetsSdkVersionRequirement( 131 currentProgramInfo, uid)) { 132 Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send " 133 + "program info requiring higher target SDK version"); 134 return; 135 } 136 cb.onCurrentProgramInfoChanged(currentProgramInfo); 137 }); 138 } 139 }); 140 } 141 142 @Override 143 public void onProgramListUpdated(ProgramListChunk programListChunk) { 144 fireLater(() -> { 145 synchronized (mLock) { 146 mProgramInfoCache.filterAndApplyChunk(programListChunk); 147 148 for (int i = 0; i < mAidlTunerSessions.size(); i++) { 149 mAidlTunerSessions.valueAt(i).onMergedProgramListUpdateFromHal( 150 programListChunk); 151 } 152 } 153 }); 154 } 155 156 @Override 157 public void onAntennaStateChange(boolean connected) { 158 fireLater(() -> { 159 synchronized (mLock) { 160 mAntennaConnected = connected; 161 fanoutAidlCallbackLocked((cb, uid) -> cb.onAntennaState(connected)); 162 } 163 }); 164 } 165 166 @Override 167 public void onConfigFlagUpdated(int flag, boolean value) { 168 fireLater(() -> { 169 synchronized (mLock) { 170 fanoutAidlCallbackLocked((cb, uid) -> { 171 if (!ConversionUtils.configFlagMeetsSdkVersionRequirement(flag, uid)) { 172 Slogf.e(TAG, "onConfigFlagUpdated: cannot send program info " 173 + "requiring higher target SDK version"); 174 return; 175 } 176 cb.onConfigFlagUpdated(flag, value); 177 }); 178 } 179 }); 180 } 181 182 @Override 183 public void onParametersUpdated(VendorKeyValue[] parameters) { 184 fireLater(() -> { 185 synchronized (mLock) { 186 Map<String, String> cparam = 187 ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters); 188 fanoutAidlCallbackLocked((cb, uid) -> { 189 cb.onParametersUpdated(cparam); 190 }); 191 } 192 }); 193 } 194 }; 195 196 @VisibleForTesting RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties)197 RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties) { 198 mProperties = Objects.requireNonNull(properties, "properties cannot be null"); 199 mService = Objects.requireNonNull(service, "service cannot be null"); 200 mHandler = new Handler(Looper.getMainLooper()); 201 mLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE); 202 } 203 204 @Nullable tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder)205 static RadioModule tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder) { 206 try { 207 Slogf.i(TAG, "Try loading module for module id = %d, module name = %s", 208 moduleId, moduleName); 209 IBroadcastRadio service = IBroadcastRadio.Stub 210 .asInterface(serviceBinder); 211 if (service == null) { 212 Slogf.w(TAG, "Module %s is null", moduleName); 213 return null; 214 } 215 216 AmFmRegionConfig amfmConfig; 217 try { 218 amfmConfig = service.getAmFmRegionConfig(/* full= */ false); 219 } catch (RuntimeException ex) { 220 Slogf.i(TAG, "Module %s does not has AMFM config", moduleName); 221 amfmConfig = null; 222 } 223 224 DabTableEntry[] dabConfig; 225 try { 226 dabConfig = service.getDabRegionConfig(); 227 } catch (RuntimeException ex) { 228 Slogf.i(TAG, "Module %s does not has DAB config", moduleName); 229 dabConfig = null; 230 } 231 232 RadioManager.ModuleProperties prop = ConversionUtils.propertiesFromHalProperties( 233 moduleId, moduleName, service.getProperties(), amfmConfig, dabConfig); 234 235 return new RadioModule(service, prop); 236 } catch (RemoteException ex) { 237 Slogf.e(TAG, ex, "Failed to load module %s", moduleName); 238 return null; 239 } 240 } 241 getService()242 IBroadcastRadio getService() { 243 return mService; 244 } 245 getProperties()246 RadioManager.ModuleProperties getProperties() { 247 return mProperties; 248 } 249 openSession(android.hardware.radio.ITunerCallback userCb)250 TunerSession openSession(android.hardware.radio.ITunerCallback userCb) 251 throws RemoteException { 252 mLogger.logRadioEvent("Open TunerSession"); 253 TunerSession tunerSession; 254 Boolean antennaConnected; 255 RadioManager.ProgramInfo currentProgramInfo; 256 synchronized (mLock) { 257 boolean isFirstTunerSession = mAidlTunerSessions.isEmpty(); 258 tunerSession = new TunerSession(this, mService, userCb); 259 mAidlTunerSessions.add(tunerSession); 260 antennaConnected = mAntennaConnected; 261 currentProgramInfo = mCurrentProgramInfo; 262 if (isFirstTunerSession) { 263 mService.setTunerCallback(mHalTunerCallback); 264 } 265 } 266 // Propagate state to new client. 267 // Note: These callbacks are invoked while holding mLock to prevent race conditions 268 // with new callbacks from the HAL. 269 if (antennaConnected != null) { 270 userCb.onAntennaState(antennaConnected); 271 } 272 if (currentProgramInfo != null) { 273 userCb.onCurrentProgramInfoChanged(currentProgramInfo); 274 } 275 276 return tunerSession; 277 } 278 closeSessions(int error)279 void closeSessions(int error) { 280 mLogger.logRadioEvent("Close TunerSessions %d", error); 281 // TunerSession.close() must be called without mAidlTunerSessions locked because 282 // it can call onTunerSessionClosed(). Therefore, the contents of mAidlTunerSessions 283 // are copied into a local array here. 284 TunerSession[] tunerSessions; 285 synchronized (mLock) { 286 tunerSessions = new TunerSession[mAidlTunerSessions.size()]; 287 mAidlTunerSessions.toArray(tunerSessions); 288 } 289 290 for (TunerSession tunerSession : tunerSessions) { 291 try { 292 tunerSession.close(error); 293 } catch (Exception e) { 294 Slogf.e(TAG, "Failed to close TunerSession %s: %s", tunerSession, e); 295 } 296 } 297 } 298 299 @GuardedBy("mLock") 300 @Nullable buildUnionOfTunerSessionFiltersLocked()301 private android.hardware.radio.ProgramList.Filter buildUnionOfTunerSessionFiltersLocked() { 302 Set<Integer> idTypes = null; 303 Set<android.hardware.radio.ProgramSelector.Identifier> ids = null; 304 boolean includeCategories = false; 305 boolean excludeModifications = true; 306 307 for (int i = 0; i < mAidlTunerSessions.size(); i++) { 308 android.hardware.radio.ProgramList.Filter filter = 309 mAidlTunerSessions.valueAt(i).getProgramListFilter(); 310 if (filter == null) { 311 continue; 312 } 313 314 if (idTypes == null) { 315 idTypes = new ArraySet<>(filter.getIdentifierTypes()); 316 ids = new ArraySet<>(filter.getIdentifiers()); 317 includeCategories = filter.areCategoriesIncluded(); 318 excludeModifications = filter.areModificationsExcluded(); 319 continue; 320 } 321 if (!idTypes.isEmpty()) { 322 if (filter.getIdentifierTypes().isEmpty()) { 323 idTypes.clear(); 324 } else { 325 idTypes.addAll(filter.getIdentifierTypes()); 326 } 327 } 328 329 if (!ids.isEmpty()) { 330 if (filter.getIdentifiers().isEmpty()) { 331 ids.clear(); 332 } else { 333 ids.addAll(filter.getIdentifiers()); 334 } 335 } 336 337 includeCategories |= filter.areCategoriesIncluded(); 338 excludeModifications &= filter.areModificationsExcluded(); 339 } 340 341 return idTypes == null ? null : new android.hardware.radio.ProgramList.Filter(idTypes, ids, 342 includeCategories, excludeModifications); 343 } 344 onTunerSessionProgramListFilterChanged(@ullable TunerSession session)345 void onTunerSessionProgramListFilterChanged(@Nullable TunerSession session) { 346 synchronized (mLock) { 347 onTunerSessionProgramListFilterChangedLocked(session); 348 } 349 } 350 351 @GuardedBy("mLock") onTunerSessionProgramListFilterChangedLocked(@ullable TunerSession session)352 private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) { 353 android.hardware.radio.ProgramList.Filter newFilter = 354 buildUnionOfTunerSessionFiltersLocked(); 355 if (newFilter == null) { 356 // If there are no AIDL clients remaining, we can stop updates from the HAL as well. 357 if (mUnionOfAidlProgramFilters == null) { 358 return; 359 } 360 mUnionOfAidlProgramFilters = null; 361 try { 362 mService.stopProgramListUpdates(); 363 } catch (RemoteException ex) { 364 Slogf.e(TAG, ex, "mHalTunerSession.stopProgramListUpdates() failed"); 365 } 366 return; 367 } 368 369 synchronized (mLock) { 370 // If the HAL filter doesn't change, we can immediately send an update to the AIDL 371 // client. 372 if (newFilter.equals(mUnionOfAidlProgramFilters)) { 373 if (session != null) { 374 session.updateProgramInfoFromHalCache(mProgramInfoCache); 375 } 376 return; 377 } 378 379 // Otherwise, update the HAL's filter, and AIDL clients will be updated when 380 // mHalTunerCallback.onProgramListUpdated() is called. 381 mUnionOfAidlProgramFilters = newFilter; 382 } 383 try { 384 mService.startProgramListUpdates( 385 ConversionUtils.filterToHalProgramFilter(newFilter)); 386 } catch (RuntimeException ex) { 387 throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates"); 388 } catch (RemoteException ex) { 389 Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed"); 390 } 391 } 392 onTunerSessionClosed(TunerSession tunerSession)393 void onTunerSessionClosed(TunerSession tunerSession) { 394 synchronized (mLock) { 395 onTunerSessionsClosedLocked(tunerSession); 396 } 397 } 398 399 @GuardedBy("mLock") onTunerSessionsClosedLocked(TunerSession... tunerSessions)400 private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) { 401 for (TunerSession tunerSession : tunerSessions) { 402 mAidlTunerSessions.remove(tunerSession); 403 } 404 onTunerSessionProgramListFilterChanged(null); 405 if (mAidlTunerSessions.isEmpty()) { 406 try { 407 mService.unsetTunerCallback(); 408 } catch (RemoteException ex) { 409 Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d", 410 mProperties.getId()); 411 } 412 } 413 } 414 415 // add to mHandler queue fireLater(Runnable r)416 private void fireLater(Runnable r) { 417 mHandler.post(() -> r.run()); 418 } 419 420 interface AidlCallbackRunnable { run(android.hardware.radio.ITunerCallback callback, int uid)421 void run(android.hardware.radio.ITunerCallback callback, int uid) 422 throws RemoteException; 423 } 424 425 // Invokes runnable with each TunerSession currently open. fanoutAidlCallback(AidlCallbackRunnable runnable)426 void fanoutAidlCallback(AidlCallbackRunnable runnable) { 427 fireLater(() -> { 428 synchronized (mLock) { 429 fanoutAidlCallbackLocked(runnable); 430 } 431 }); 432 } 433 434 @GuardedBy("mLock") fanoutAidlCallbackLocked(AidlCallbackRunnable runnable)435 private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) { 436 int currentUserId = RadioServiceUserController.getCurrentUser(); 437 List<TunerSession> deadSessions = null; 438 for (int i = 0; i < mAidlTunerSessions.size(); i++) { 439 if (mAidlTunerSessions.valueAt(i).mUserId != currentUserId 440 && mAidlTunerSessions.valueAt(i).mUserId != UserHandle.USER_SYSTEM) { 441 continue; 442 } 443 try { 444 runnable.run(mAidlTunerSessions.valueAt(i).mCallback, 445 mAidlTunerSessions.valueAt(i).getUid()); 446 } catch (DeadObjectException ex) { 447 // The other side died without calling close(), so just purge it from our records. 448 Slogf.e(TAG, "Removing dead TunerSession"); 449 if (deadSessions == null) { 450 deadSessions = new ArrayList<>(); 451 } 452 deadSessions.add(mAidlTunerSessions.valueAt(i)); 453 } catch (RemoteException ex) { 454 Slogf.e(TAG, ex, "Failed to invoke ITunerCallback"); 455 } 456 } 457 if (deadSessions != null) { 458 onTunerSessionsClosedLocked(deadSessions.toArray( 459 new TunerSession[deadSessions.size()])); 460 } 461 } 462 addAnnouncementListener( android.hardware.radio.IAnnouncementListener listener, int[] enabledTypes)463 android.hardware.radio.ICloseHandle addAnnouncementListener( 464 android.hardware.radio.IAnnouncementListener listener, 465 int[] enabledTypes) throws RemoteException { 466 mLogger.logRadioEvent("Add AnnouncementListener"); 467 byte[] enabledList = new byte[enabledTypes.length]; 468 for (int index = 0; index < enabledList.length; index++) { 469 enabledList[index] = (byte) enabledTypes[index]; 470 } 471 472 final ICloseHandle[] hwCloseHandle = {null}; 473 IAnnouncementListener hwListener = new IAnnouncementListener.Stub() { 474 public int getInterfaceVersion() { 475 return this.VERSION; 476 } 477 478 public String getInterfaceHash() { 479 return this.HASH; 480 } 481 482 public void onListUpdated(Announcement[] hwAnnouncements) 483 throws RemoteException { 484 List<android.hardware.radio.Announcement> announcements = 485 new ArrayList<>(hwAnnouncements.length); 486 for (int i = 0; i < hwAnnouncements.length; i++) { 487 announcements.add( 488 ConversionUtils.announcementFromHalAnnouncement(hwAnnouncements[i])); 489 } 490 listener.onListUpdated(announcements); 491 } 492 }; 493 494 try { 495 hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList); 496 } catch (RuntimeException ex) { 497 throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener"); 498 } 499 500 return new android.hardware.radio.ICloseHandle.Stub() { 501 public void close() { 502 try { 503 hwCloseHandle[0].close(); 504 } catch (RemoteException ex) { 505 Slogf.e(TAG, ex, "Failed closing announcement listener"); 506 } 507 hwCloseHandle[0] = null; 508 } 509 }; 510 } 511 512 Bitmap getImage(int id) { 513 mLogger.logRadioEvent("Get image for id = %d", id); 514 if (id == 0) throw new IllegalArgumentException("Image ID is missing"); 515 516 byte[] rawImage; 517 try { 518 rawImage = mService.getImage(id); 519 } catch (RemoteException ex) { 520 throw ex.rethrowFromSystemServer(); 521 } 522 523 if (rawImage == null || rawImage.length == 0) return null; 524 525 return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length); 526 } 527 528 void dumpInfo(IndentingPrintWriter pw) { 529 pw.printf("RadioModule\n"); 530 531 pw.increaseIndent(); 532 synchronized (mLock) { 533 pw.printf("BroadcastRadioServiceImpl: %s\n", mService); 534 pw.printf("Properties: %s\n", mProperties); 535 pw.printf("Antenna state: "); 536 if (mAntennaConnected == null) { 537 pw.printf("undetermined\n"); 538 } else { 539 pw.printf("%s\n", mAntennaConnected ? "connected" : "not connected"); 540 } 541 pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo); 542 pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache); 543 pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters); 544 pw.printf("AIDL TunerSessions [%d]:\n", mAidlTunerSessions.size()); 545 546 pw.increaseIndent(); 547 for (int i = 0; i < mAidlTunerSessions.size(); i++) { 548 mAidlTunerSessions.valueAt(i).dumpInfo(pw); 549 } 550 pw.decreaseIndent(); 551 } 552 pw.printf("Radio module events:\n"); 553 554 pw.increaseIndent(); 555 mLogger.dump(pw); 556 pw.decreaseIndent(); 557 558 pw.decreaseIndent(); 559 } 560 } 561