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