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.hardware.broadcastradio.V2_0.ConfigFlag; 23 import android.hardware.broadcastradio.V2_0.ITunerSession; 24 import android.hardware.broadcastradio.V2_0.Result; 25 import android.hardware.radio.ITuner; 26 import android.hardware.radio.ProgramList; 27 import android.hardware.radio.ProgramSelector; 28 import android.hardware.radio.RadioManager; 29 import android.os.RemoteException; 30 import android.util.IndentingPrintWriter; 31 import android.util.MutableBoolean; 32 import android.util.MutableInt; 33 import android.util.Slog; 34 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Objects; 39 40 class TunerSession extends ITuner.Stub { 41 private static final String TAG = "BcRadio2Srv.session"; 42 private static final String kAudioDeviceName = "Radio tuner source"; 43 private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25; 44 45 private final Object mLock; 46 @NonNull private final RadioEventLogger mEventLogger; 47 48 private final RadioModule mModule; 49 private final ITunerSession mHwSession; 50 final android.hardware.radio.ITunerCallback mCallback; 51 private boolean mIsClosed = false; 52 private boolean mIsMuted = false; 53 private ProgramInfoCache mProgramInfoCache = null; 54 55 // necessary only for older APIs compatibility 56 private RadioManager.BandConfig mDummyConfig = null; 57 TunerSession(@onNull RadioModule module, @NonNull ITunerSession hwSession, @NonNull android.hardware.radio.ITunerCallback callback, @NonNull Object lock)58 TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession, 59 @NonNull android.hardware.radio.ITunerCallback callback, 60 @NonNull Object lock) { 61 mModule = Objects.requireNonNull(module); 62 mHwSession = Objects.requireNonNull(hwSession); 63 mCallback = Objects.requireNonNull(callback); 64 mLock = Objects.requireNonNull(lock); 65 mEventLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE); 66 } 67 68 @Override close()69 public void close() { 70 mEventLogger.logRadioEvent("Close"); 71 close(null); 72 } 73 74 /** 75 * Closes the TunerSession. If error is non-null, the client's onError() callback is invoked 76 * first with the specified error, see {@link 77 * android.hardware.radio.RadioTuner.Callback#onError}. 78 * 79 * @param error Optional error to send to client before session is closed. 80 */ close(@ullable Integer error)81 public void close(@Nullable Integer error) { 82 mEventLogger.logRadioEvent("Close on error %d", error); 83 synchronized (mLock) { 84 if (mIsClosed) return; 85 if (error != null) { 86 try { 87 mCallback.onError(error); 88 } catch (RemoteException ex) { 89 Slog.w(TAG, "mCallback.onError() failed: ", ex); 90 } 91 } 92 mIsClosed = true; 93 mModule.onTunerSessionClosed(this); 94 } 95 } 96 97 @Override isClosed()98 public boolean isClosed() { 99 return mIsClosed; 100 } 101 checkNotClosedLocked()102 private void checkNotClosedLocked() { 103 if (mIsClosed) { 104 throw new IllegalStateException("Tuner is closed, no further operations are allowed"); 105 } 106 } 107 108 @Override setConfiguration(RadioManager.BandConfig config)109 public void setConfiguration(RadioManager.BandConfig config) { 110 synchronized (mLock) { 111 checkNotClosedLocked(); 112 mDummyConfig = Objects.requireNonNull(config); 113 Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0"); 114 mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config)); 115 } 116 } 117 118 @Override getConfiguration()119 public RadioManager.BandConfig getConfiguration() { 120 synchronized (mLock) { 121 checkNotClosedLocked(); 122 return mDummyConfig; 123 } 124 } 125 126 @Override setMuted(boolean mute)127 public void setMuted(boolean mute) { 128 synchronized (mLock) { 129 checkNotClosedLocked(); 130 if (mIsMuted == mute) return; 131 mIsMuted = mute; 132 Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); 133 } 134 } 135 136 @Override isMuted()137 public boolean isMuted() { 138 synchronized (mLock) { 139 checkNotClosedLocked(); 140 return mIsMuted; 141 } 142 } 143 144 @Override step(boolean directionDown, boolean skipSubChannel)145 public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException { 146 mEventLogger.logRadioEvent("Step with direction %s, skipSubChannel? %s", 147 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no"); 148 synchronized (mLock) { 149 checkNotClosedLocked(); 150 int halResult = mHwSession.step(!directionDown); 151 Convert.throwOnError("step", halResult); 152 } 153 } 154 155 @Override scan(boolean directionDown, boolean skipSubChannel)156 public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException { 157 mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s", 158 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no"); 159 synchronized (mLock) { 160 checkNotClosedLocked(); 161 int halResult = mHwSession.scan(!directionDown, skipSubChannel); 162 Convert.throwOnError("step", halResult); 163 } 164 } 165 166 @Override tune(ProgramSelector selector)167 public void tune(ProgramSelector selector) throws RemoteException { 168 mEventLogger.logRadioEvent("Tune with selector %s", selector); 169 synchronized (mLock) { 170 checkNotClosedLocked(); 171 int halResult = mHwSession.tune(Convert.programSelectorToHal(selector)); 172 Convert.throwOnError("tune", halResult); 173 } 174 } 175 176 @Override cancel()177 public void cancel() { 178 Slog.i(TAG, "Cancel"); 179 synchronized (mLock) { 180 checkNotClosedLocked(); 181 Utils.maybeRethrow(mHwSession::cancel); 182 } 183 } 184 185 @Override cancelAnnouncement()186 public void cancelAnnouncement() { 187 Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0"); 188 } 189 190 @Override getImage(int id)191 public Bitmap getImage(int id) { 192 mEventLogger.logRadioEvent("Get image for %d", id); 193 return mModule.getImage(id); 194 } 195 196 @Override startBackgroundScan()197 public boolean startBackgroundScan() { 198 Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0"); 199 mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete()); 200 return true; 201 } 202 203 @Override startProgramListUpdates(ProgramList.Filter filter)204 public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException { 205 mEventLogger.logRadioEvent("start programList updates %s", filter); 206 // If the AIDL client provides a null filter, it wants all updates, so use the most broad 207 // filter. 208 if (filter == null) { 209 filter = new ProgramList.Filter(new HashSet<Integer>(), 210 new HashSet<android.hardware.radio.ProgramSelector.Identifier>(), true, false); 211 } 212 synchronized (mLock) { 213 checkNotClosedLocked(); 214 mProgramInfoCache = new ProgramInfoCache(filter); 215 } 216 // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock 217 // held since it can call getProgramListFilter() and onHalProgramInfoUpdated(). 218 mModule.onTunerSessionProgramListFilterChanged(this); 219 } 220 getProgramListFilter()221 ProgramList.Filter getProgramListFilter() { 222 synchronized (mLock) { 223 return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter(); 224 } 225 } 226 onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk)227 void onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk) { 228 List<ProgramList.Chunk> clientUpdateChunks = null; 229 synchronized (mLock) { 230 if (mProgramInfoCache == null) { 231 return; 232 } 233 clientUpdateChunks = mProgramInfoCache.filterAndApplyChunk(mergedChunk); 234 } 235 dispatchClientUpdateChunks(clientUpdateChunks); 236 } 237 updateProgramInfoFromHalCache(ProgramInfoCache halCache)238 void updateProgramInfoFromHalCache(ProgramInfoCache halCache) { 239 List<ProgramList.Chunk> clientUpdateChunks = null; 240 synchronized (mLock) { 241 if (mProgramInfoCache == null) { 242 return; 243 } 244 clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, true); 245 } 246 dispatchClientUpdateChunks(clientUpdateChunks); 247 } 248 dispatchClientUpdateChunks(@ullable List<ProgramList.Chunk> chunks)249 private void dispatchClientUpdateChunks(@Nullable List<ProgramList.Chunk> chunks) { 250 if (chunks == null) { 251 return; 252 } 253 for (ProgramList.Chunk chunk : chunks) { 254 try { 255 mCallback.onProgramListUpdated(chunk); 256 } catch (RemoteException ex) { 257 Slog.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex); 258 } 259 } 260 } 261 262 @Override stopProgramListUpdates()263 public void stopProgramListUpdates() throws RemoteException { 264 mEventLogger.logRadioEvent("Stop programList updates"); 265 synchronized (mLock) { 266 checkNotClosedLocked(); 267 mProgramInfoCache = null; 268 } 269 // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock 270 // held since it can call getProgramListFilter() and onHalProgramInfoUpdated(). 271 mModule.onTunerSessionProgramListFilterChanged(this); 272 } 273 274 @Override isConfigFlagSupported(int flag)275 public boolean isConfigFlagSupported(int flag) { 276 try { 277 isConfigFlagSet(flag); 278 return true; 279 } catch (IllegalStateException ex) { 280 return true; 281 } catch (UnsupportedOperationException ex) { 282 return false; 283 } 284 } 285 286 @Override isConfigFlagSet(int flag)287 public boolean isConfigFlagSet(int flag) { 288 mEventLogger.logRadioEvent("Is ConfigFlagSet for %s", ConfigFlag.toString(flag)); 289 synchronized (mLock) { 290 checkNotClosedLocked(); 291 292 MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); 293 MutableBoolean flagState = new MutableBoolean(false); 294 try { 295 mHwSession.isConfigFlagSet(flag, (int result, boolean value) -> { 296 halResult.value = result; 297 flagState.value = value; 298 }); 299 } catch (RemoteException ex) { 300 throw new RuntimeException("Failed to check flag " + ConfigFlag.toString(flag), ex); 301 } 302 Convert.throwOnError("isConfigFlagSet", halResult.value); 303 304 return flagState.value; 305 } 306 } 307 308 @Override setConfigFlag(int flag, boolean value)309 public void setConfigFlag(int flag, boolean value) throws RemoteException { 310 mEventLogger.logRadioEvent("Set ConfigFlag %s = %b", ConfigFlag.toString(flag), value); 311 synchronized (mLock) { 312 checkNotClosedLocked(); 313 int halResult = mHwSession.setConfigFlag(flag, value); 314 Convert.throwOnError("setConfigFlag", halResult); 315 } 316 } 317 318 @Override setParameters(Map<String, String> parameters)319 public Map<String, String> setParameters(Map<String, String> parameters) { 320 synchronized (mLock) { 321 checkNotClosedLocked(); 322 return Convert.vendorInfoFromHal(Utils.maybeRethrow( 323 () -> mHwSession.setParameters(Convert.vendorInfoToHal(parameters)))); 324 } 325 } 326 327 @Override getParameters(List<String> keys)328 public Map<String, String> getParameters(List<String> keys) { 329 synchronized (mLock) { 330 checkNotClosedLocked(); 331 return Convert.vendorInfoFromHal(Utils.maybeRethrow( 332 () -> mHwSession.getParameters(Convert.listToArrayList(keys)))); 333 } 334 } 335 dumpInfo(IndentingPrintWriter pw)336 void dumpInfo(IndentingPrintWriter pw) { 337 pw.printf("TunerSession\n"); 338 pw.increaseIndent(); 339 synchronized (mLock) { 340 pw.printf("HIDL HAL Session: %s\n", mHwSession); 341 pw.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No"); 342 pw.printf("Is muted? %s\n", mIsMuted ? "Yes" : "No"); 343 pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache); 344 pw.printf("Config: %s\n", mDummyConfig); 345 } 346 pw.printf("Tuner session events:\n"); 347 pw.increaseIndent(); 348 mEventLogger.dump(pw); 349 pw.decreaseIndent(); 350 pw.decreaseIndent(); 351 } 352 } 353