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