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.RemoteException; 39 import android.util.MutableInt; 40 import android.util.Slog; 41 42 import com.android.internal.annotations.GuardedBy; 43 44 import java.util.ArrayList; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Objects; 48 import java.util.Set; 49 import java.util.stream.Collectors; 50 51 class RadioModule { 52 private static final String TAG = "BcRadio2Srv.module"; 53 54 @NonNull private final IBroadcastRadio mService; 55 @NonNull public final RadioManager.ModuleProperties mProperties; 56 57 private final Object mLock = new Object(); 58 59 @GuardedBy("mLock") 60 private ITunerSession mHalTunerSession; 61 62 // Tracks antenna state reported by HAL (if any). 63 @GuardedBy("mLock") 64 private Boolean mAntennaConnected = null; 65 66 @GuardedBy("mLock") 67 private RadioManager.ProgramInfo mProgramInfo = null; 68 69 // Callback registered with the HAL to relay callbacks to AIDL clients. 70 private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() { 71 @Override 72 public void onTuneFailed(int result, ProgramSelector programSelector) { 73 fanoutAidlCallback(cb -> cb.onTuneFailed(result, Convert.programSelectorFromHal( 74 programSelector))); 75 } 76 77 @Override 78 public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) { 79 RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo); 80 synchronized (mLock) { 81 mProgramInfo = programInfo; 82 fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(programInfo)); 83 } 84 } 85 86 @Override 87 public void onProgramListUpdated(ProgramListChunk programListChunk) { 88 // TODO: Cache per-AIDL client filters, send union of filters to HAL, use filters to fan 89 // back out to clients. 90 fanoutAidlCallback(cb -> cb.onProgramListUpdated(Convert.programListChunkFromHal( 91 programListChunk))); 92 } 93 94 @Override 95 public void onAntennaStateChange(boolean connected) { 96 synchronized (mLock) { 97 mAntennaConnected = connected; 98 fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected)); 99 } 100 } 101 102 @Override 103 public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) { 104 fanoutAidlCallback(cb -> cb.onParametersUpdated(Convert.vendorInfoFromHal(parameters))); 105 } 106 }; 107 108 // Collection of active AIDL tuner sessions created through openSession(). 109 @GuardedBy("mLock") 110 private final Set<TunerSession> mAidlTunerSessions = new HashSet<>(); 111 RadioModule(@onNull IBroadcastRadio service, @NonNull RadioManager.ModuleProperties properties)112 private RadioModule(@NonNull IBroadcastRadio service, 113 @NonNull RadioManager.ModuleProperties properties) throws RemoteException { 114 mProperties = Objects.requireNonNull(properties); 115 mService = Objects.requireNonNull(service); 116 } 117 tryLoadingModule(int idx, @NonNull String fqName)118 public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) { 119 try { 120 IBroadcastRadio service = IBroadcastRadio.getService(fqName); 121 if (service == null) return null; 122 123 Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>(); 124 service.getAmFmRegionConfig(false, (result, config) -> { 125 if (result == Result.OK) amfmConfig.value = config; 126 }); 127 128 Mutable<List<DabTableEntry>> dabConfig = new Mutable<>(); 129 service.getDabRegionConfig((result, config) -> { 130 if (result == Result.OK) dabConfig.value = config; 131 }); 132 133 RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName, 134 service.getProperties(), amfmConfig.value, dabConfig.value); 135 136 return new RadioModule(service, prop); 137 } catch (RemoteException ex) { 138 Slog.e(TAG, "failed to load module " + fqName, ex); 139 return null; 140 } 141 } 142 getService()143 public @NonNull IBroadcastRadio getService() { 144 return mService; 145 } 146 openSession(@onNull android.hardware.radio.ITunerCallback userCb)147 public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) 148 throws RemoteException { 149 synchronized (mLock) { 150 if (mHalTunerSession == null) { 151 Mutable<ITunerSession> hwSession = new Mutable<>(); 152 mService.openSession(mHalTunerCallback, (result, session) -> { 153 Convert.throwOnError("openSession", result); 154 hwSession.value = session; 155 }); 156 mHalTunerSession = Objects.requireNonNull(hwSession.value); 157 } 158 TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb); 159 mAidlTunerSessions.add(tunerSession); 160 161 // Propagate state to new client. Note: These callbacks are invoked while holding mLock 162 // to prevent race conditions with new callbacks from the HAL. 163 if (mAntennaConnected != null) { 164 userCb.onAntennaState(mAntennaConnected); 165 } 166 if (mProgramInfo != null) { 167 userCb.onCurrentProgramInfoChanged(mProgramInfo); 168 } 169 170 return tunerSession; 171 } 172 } 173 closeSessions(Integer error)174 public void closeSessions(Integer error) { 175 // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close() 176 // must be called without mAidlTunerSessions locked because it can call 177 // onTunerSessionClosed(). 178 TunerSession[] tunerSessions; 179 synchronized (mLock) { 180 tunerSessions = new TunerSession[mAidlTunerSessions.size()]; 181 mAidlTunerSessions.toArray(tunerSessions); 182 mAidlTunerSessions.clear(); 183 } 184 for (TunerSession tunerSession : tunerSessions) { 185 tunerSession.close(error); 186 } 187 } 188 onTunerSessionClosed(TunerSession tunerSession)189 void onTunerSessionClosed(TunerSession tunerSession) { 190 synchronized (mLock) { 191 mAidlTunerSessions.remove(tunerSession); 192 if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) { 193 Slog.v(TAG, "closing HAL tuner session"); 194 try { 195 mHalTunerSession.close(); 196 } catch (RemoteException ex) { 197 Slog.e(TAG, "mHalTunerSession.close() failed: ", ex); 198 } 199 mHalTunerSession = null; 200 } 201 } 202 } 203 204 interface AidlCallbackRunnable { run(android.hardware.radio.ITunerCallback callback)205 void run(android.hardware.radio.ITunerCallback callback) throws RemoteException; 206 } 207 208 // Invokes runnable with each TunerSession currently open. fanoutAidlCallback(AidlCallbackRunnable runnable)209 void fanoutAidlCallback(AidlCallbackRunnable runnable) { 210 synchronized (mLock) { 211 fanoutAidlCallbackLocked(runnable); 212 } 213 } 214 fanoutAidlCallbackLocked(AidlCallbackRunnable runnable)215 private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) { 216 for (TunerSession tunerSession : mAidlTunerSessions) { 217 try { 218 runnable.run(tunerSession.mCallback); 219 } catch (DeadObjectException ex) { 220 // The other side died without calling close(), so just purge it from our 221 // records. 222 Slog.e(TAG, "Removing dead TunerSession"); 223 mAidlTunerSessions.remove(tunerSession); 224 } catch (RemoteException ex) { 225 Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex); 226 } 227 } 228 } 229 addAnnouncementListener(@onNull int[] enabledTypes, @NonNull android.hardware.radio.IAnnouncementListener listener)230 public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes, 231 @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException { 232 ArrayList<Byte> enabledList = new ArrayList<>(); 233 for (int type : enabledTypes) { 234 enabledList.add((byte)type); 235 } 236 237 MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); 238 Mutable<ICloseHandle> hwCloseHandle = new Mutable<>(); 239 IAnnouncementListener hwListener = new IAnnouncementListener.Stub() { 240 public void onListUpdated(ArrayList<Announcement> hwAnnouncements) 241 throws RemoteException { 242 listener.onListUpdated(hwAnnouncements.stream(). 243 map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList())); 244 } 245 }; 246 247 synchronized (mService) { 248 mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> { 249 halResult.value = result; 250 hwCloseHandle.value = closeHnd; 251 }); 252 } 253 Convert.throwOnError("addAnnouncementListener", halResult.value); 254 255 return new android.hardware.radio.ICloseHandle.Stub() { 256 public void close() { 257 try { 258 hwCloseHandle.value.close(); 259 } catch (RemoteException ex) { 260 Slog.e(TAG, "Failed closing announcement listener", ex); 261 } 262 } 263 }; 264 } 265 266 Bitmap getImage(int id) { 267 if (id == 0) throw new IllegalArgumentException("Image ID is missing"); 268 269 byte[] rawImage; 270 synchronized (mService) { 271 List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id)); 272 rawImage = new byte[rawList.size()]; 273 for (int i = 0; i < rawList.size(); i++) { 274 rawImage[i] = rawList.get(i); 275 } 276 } 277 278 if (rawImage == null || rawImage.length == 0) return null; 279 280 return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length); 281 } 282 } 283