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