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