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.hal1; 18 19 import android.annotation.NonNull; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.hardware.radio.ITuner; 23 import android.hardware.radio.ITunerCallback; 24 import android.hardware.radio.ProgramList; 25 import android.hardware.radio.ProgramSelector; 26 import android.hardware.radio.RadioManager; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.util.Slog; 30 31 import com.android.server.broadcastradio.RadioServiceUserController; 32 import com.android.server.utils.Slogf; 33 34 import java.util.List; 35 import java.util.Map; 36 37 class Tuner extends ITuner.Stub { 38 39 private static final String TAG = "BcRadio1Srv.Tuner"; 40 41 /** 42 * This field is used by native code, do not access or modify. 43 */ 44 private final long mNativeContext; 45 46 private final Object mLock = new Object(); 47 @NonNull private final TunerCallback mTunerCallback; 48 @NonNull private final ITunerCallback mClientCallback; 49 @NonNull private final IBinder.DeathRecipient mDeathRecipient; 50 51 private boolean mIsClosed = false; 52 private boolean mIsMuted = false; 53 private int mRegion; 54 private final boolean mWithAudio; 55 Tuner(@onNull ITunerCallback clientCallback, int halRev, int region, boolean withAudio, int band)56 Tuner(@NonNull ITunerCallback clientCallback, int halRev, 57 int region, boolean withAudio, int band) { 58 mClientCallback = clientCallback; 59 mTunerCallback = new TunerCallback(this, clientCallback, halRev); 60 mRegion = region; 61 mWithAudio = withAudio; 62 mNativeContext = nativeInit(halRev, withAudio, band); 63 mDeathRecipient = this::close; 64 try { 65 mClientCallback.asBinder().linkToDeath(mDeathRecipient, 0); 66 } catch (RemoteException ex) { 67 close(); 68 } 69 } 70 71 @Override finalize()72 protected void finalize() throws Throwable { 73 nativeFinalize(mNativeContext); 74 super.finalize(); 75 } 76 nativeInit(int halRev, boolean withAudio, int band)77 private native long nativeInit(int halRev, boolean withAudio, int band); nativeFinalize(long nativeContext)78 private native void nativeFinalize(long nativeContext); nativeClose(long nativeContext)79 private native void nativeClose(long nativeContext); 80 nativeSetConfiguration(long nativeContext, @NonNull RadioManager.BandConfig config)81 private native void nativeSetConfiguration(long nativeContext, 82 @NonNull RadioManager.BandConfig config); nativeGetConfiguration(long nativeContext, int region)83 private native RadioManager.BandConfig nativeGetConfiguration(long nativeContext, int region); 84 nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel)85 private native void nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel); nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel)86 private native void nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel); nativeTune(long nativeContext, @NonNull ProgramSelector selector)87 private native void nativeTune(long nativeContext, @NonNull ProgramSelector selector); nativeCancel(long nativeContext)88 private native void nativeCancel(long nativeContext); 89 nativeCancelAnnouncement(long nativeContext)90 private native void nativeCancelAnnouncement(long nativeContext); 91 nativeStartBackgroundScan(long nativeContext)92 private native boolean nativeStartBackgroundScan(long nativeContext); nativeGetProgramList(long nativeContext, Map<String, String> vendorFilter)93 private native List<RadioManager.ProgramInfo> nativeGetProgramList(long nativeContext, 94 Map<String, String> vendorFilter); 95 nativeGetImage(long nativeContext, int id)96 private native byte[] nativeGetImage(long nativeContext, int id); 97 nativeIsAnalogForced(long nativeContext)98 private native boolean nativeIsAnalogForced(long nativeContext); nativeSetAnalogForced(long nativeContext, boolean isForced)99 private native void nativeSetAnalogForced(long nativeContext, boolean isForced); 100 101 @Override close()102 public void close() { 103 synchronized (mLock) { 104 if (mIsClosed) return; 105 mIsClosed = true; 106 mTunerCallback.detach(); 107 mClientCallback.asBinder().unlinkToDeath(mDeathRecipient, 0); 108 nativeClose(mNativeContext); 109 } 110 } 111 112 @Override isClosed()113 public boolean isClosed() { 114 return mIsClosed; 115 } 116 checkNotClosedLocked()117 private void checkNotClosedLocked() { 118 if (mIsClosed) { 119 throw new IllegalStateException("Tuner is closed, no further operations are allowed"); 120 } 121 } 122 checkConfiguredLocked()123 private boolean checkConfiguredLocked() { 124 if (mTunerCallback.isInitialConfigurationDone()) return true; 125 Slog.w(TAG, "Initial configuration is still pending, skipping the operation"); 126 return false; 127 } 128 129 @Override setConfiguration(RadioManager.BandConfig config)130 public void setConfiguration(RadioManager.BandConfig config) { 131 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 132 Slogf.w(TAG, "Cannot set configuration for HAL 1.x client from non-current user"); 133 return; 134 } 135 if (config == null) { 136 throw new IllegalArgumentException("The argument must not be a null pointer"); 137 } 138 synchronized (mLock) { 139 checkNotClosedLocked(); 140 nativeSetConfiguration(mNativeContext, config); 141 mRegion = config.getRegion(); 142 } 143 } 144 145 @Override getConfiguration()146 public RadioManager.BandConfig getConfiguration() { 147 synchronized (mLock) { 148 checkNotClosedLocked(); 149 return nativeGetConfiguration(mNativeContext, mRegion); 150 } 151 } 152 153 @Override setMuted(boolean mute)154 public void setMuted(boolean mute) { 155 if (!mWithAudio) { 156 throw new IllegalStateException("Can't operate on mute - no audio requested"); 157 } 158 synchronized (mLock) { 159 checkNotClosedLocked(); 160 if (mIsMuted == mute) return; 161 mIsMuted = mute; 162 Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); 163 } 164 } 165 166 @Override isMuted()167 public boolean isMuted() { 168 if (!mWithAudio) { 169 Slog.w(TAG, "Tuner did not request audio, pretending it was muted"); 170 return true; 171 } 172 synchronized (mLock) { 173 checkNotClosedLocked(); 174 return mIsMuted; 175 } 176 } 177 178 @Override step(boolean directionDown, boolean skipSubChannel)179 public void step(boolean directionDown, boolean skipSubChannel) { 180 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 181 Slogf.w(TAG, "Cannot step on HAL 1.x client from non-current user"); 182 return; 183 } 184 synchronized (mLock) { 185 checkNotClosedLocked(); 186 if (!checkConfiguredLocked()) return; 187 nativeStep(mNativeContext, directionDown, skipSubChannel); 188 } 189 } 190 191 @Override seek(boolean directionDown, boolean skipSubChannel)192 public void seek(boolean directionDown, boolean skipSubChannel) { 193 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 194 Slogf.w(TAG, "Cannot seek on HAL 1.x client from non-current user"); 195 return; 196 } 197 synchronized (mLock) { 198 checkNotClosedLocked(); 199 if (!checkConfiguredLocked()) return; 200 nativeScan(mNativeContext, directionDown, skipSubChannel); 201 } 202 } 203 204 @Override tune(ProgramSelector selector)205 public void tune(ProgramSelector selector) { 206 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 207 Slogf.w(TAG, "Cannot tune on HAL 1.x client from non-current user"); 208 return; 209 } 210 if (selector == null) { 211 throw new IllegalArgumentException("The argument must not be a null pointer"); 212 } 213 Slog.i(TAG, "Tuning to " + selector); 214 synchronized (mLock) { 215 checkNotClosedLocked(); 216 if (!checkConfiguredLocked()) return; 217 nativeTune(mNativeContext, selector); 218 } 219 } 220 221 @Override cancel()222 public void cancel() { 223 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 224 Slogf.w(TAG, "Cannot cancel on HAL 1.x client from non-current user"); 225 return; 226 } 227 synchronized (mLock) { 228 checkNotClosedLocked(); 229 nativeCancel(mNativeContext); 230 } 231 } 232 233 @Override cancelAnnouncement()234 public void cancelAnnouncement() { 235 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 236 Slogf.w(TAG, "Cannot cancel announcement on HAL 1.x client from non-current user"); 237 return; 238 } 239 synchronized (mLock) { 240 checkNotClosedLocked(); 241 nativeCancelAnnouncement(mNativeContext); 242 } 243 } 244 245 @Override getImage(int id)246 public Bitmap getImage(int id) { 247 if (id == 0) { 248 throw new IllegalArgumentException("Image ID is missing"); 249 } 250 251 byte[] rawImage; 252 synchronized (mLock) { 253 rawImage = nativeGetImage(mNativeContext, id); 254 } 255 if (rawImage == null || rawImage.length == 0) { 256 return null; 257 } 258 259 return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length); 260 } 261 262 @Override startBackgroundScan()263 public boolean startBackgroundScan() { 264 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 265 Slogf.w(TAG, 266 "Cannot start background scan on HAL 1.x client from non-current user"); 267 return false; 268 } 269 synchronized (mLock) { 270 checkNotClosedLocked(); 271 return nativeStartBackgroundScan(mNativeContext); 272 } 273 } 274 getProgramList(Map vendorFilter)275 List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { 276 Map<String, String> sFilter = vendorFilter; 277 synchronized (mLock) { 278 checkNotClosedLocked(); 279 List<RadioManager.ProgramInfo> list = nativeGetProgramList(mNativeContext, sFilter); 280 if (list == null) { 281 throw new IllegalStateException("Program list is not ready"); 282 } 283 return list; 284 } 285 } 286 287 @Override startProgramListUpdates(ProgramList.Filter filter)288 public void startProgramListUpdates(ProgramList.Filter filter) { 289 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 290 Slogf.w(TAG, 291 "Cannot start program list updates on HAL 1.x client from non-current user"); 292 return; 293 } 294 mTunerCallback.startProgramListUpdates(filter); 295 } 296 297 @Override stopProgramListUpdates()298 public void stopProgramListUpdates() { 299 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 300 Slogf.w(TAG, 301 "Cannot stop program list updates on HAL 1.x client from non-current user"); 302 return; 303 } 304 mTunerCallback.stopProgramListUpdates(); 305 } 306 307 @Override isConfigFlagSupported(int flag)308 public boolean isConfigFlagSupported(int flag) { 309 return flag == RadioManager.CONFIG_FORCE_ANALOG; 310 } 311 312 @Override isConfigFlagSet(int flag)313 public boolean isConfigFlagSet(int flag) { 314 if (flag == RadioManager.CONFIG_FORCE_ANALOG) { 315 synchronized (mLock) { 316 checkNotClosedLocked(); 317 return nativeIsAnalogForced(mNativeContext); 318 } 319 } 320 throw new UnsupportedOperationException("Not supported by HAL 1.x"); 321 } 322 323 @Override setConfigFlag(int flag, boolean value)324 public void setConfigFlag(int flag, boolean value) { 325 if (!RadioServiceUserController.isCurrentOrSystemUser()) { 326 Slogf.w(TAG, "Cannot set config flag for HAL 1.x client from non-current user"); 327 return; 328 } 329 if (flag == RadioManager.CONFIG_FORCE_ANALOG) { 330 synchronized (mLock) { 331 checkNotClosedLocked(); 332 nativeSetAnalogForced(mNativeContext, value); 333 return; 334 } 335 } 336 throw new UnsupportedOperationException("Not supported by HAL 1.x"); 337 } 338 339 @Override setParameters(Map<String, String> parameters)340 public Map<String, String> setParameters(Map<String, String> parameters) { 341 throw new UnsupportedOperationException("Not supported by HAL 1.x"); 342 } 343 344 @Override getParameters(List<String> keys)345 public Map<String, String> getParameters(List<String> keys) { 346 throw new UnsupportedOperationException("Not supported by HAL 1.x"); 347 } 348 } 349