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