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