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 17package com.android.server.broadcastradio; 18 19import android.annotation.NonNull; 20import android.graphics.Bitmap; 21import android.graphics.BitmapFactory; 22import android.hardware.radio.ITuner; 23import android.hardware.radio.ITunerCallback; 24import android.hardware.radio.ProgramSelector; 25import android.hardware.radio.RadioManager; 26import android.os.IBinder; 27import android.os.RemoteException; 28import android.util.Slog; 29 30import java.util.List; 31import java.util.Map; 32 33class Tuner extends ITuner.Stub { 34 private static final String TAG = "BroadcastRadioService.Tuner"; 35 36 /** 37 * This field is used by native code, do not access or modify. 38 */ 39 private final long mNativeContext; 40 41 private final Object mLock = new Object(); 42 @NonNull private final TunerCallback mTunerCallback; 43 @NonNull private final ITunerCallback mClientCallback; 44 @NonNull private final IBinder.DeathRecipient mDeathRecipient; 45 46 private boolean mIsClosed = false; 47 private boolean mIsMuted = false; 48 private int mRegion; // TODO(b/62710330): find better solution to handle regions 49 private final boolean mWithAudio; 50 51 Tuner(@NonNull ITunerCallback clientCallback, int halRev, 52 int region, boolean withAudio, int band) { 53 mClientCallback = clientCallback; 54 mTunerCallback = new TunerCallback(this, clientCallback, halRev); 55 mRegion = region; 56 mWithAudio = withAudio; 57 mNativeContext = nativeInit(halRev, withAudio, band); 58 mDeathRecipient = this::close; 59 try { 60 mClientCallback.asBinder().linkToDeath(mDeathRecipient, 0); 61 } catch (RemoteException ex) { 62 close(); 63 } 64 } 65 66 @Override 67 protected void finalize() throws Throwable { 68 nativeFinalize(mNativeContext); 69 super.finalize(); 70 } 71 72 private native long nativeInit(int halRev, boolean withAudio, int band); 73 private native void nativeFinalize(long nativeContext); 74 private native void nativeClose(long nativeContext); 75 76 private native void nativeSetConfiguration(long nativeContext, 77 @NonNull RadioManager.BandConfig config); 78 private native RadioManager.BandConfig nativeGetConfiguration(long nativeContext, int region); 79 80 private native void nativeSetMuted(long nativeContext, boolean mute); 81 82 private native void nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel); 83 private native void nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel); 84 private native void nativeTune(long nativeContext, @NonNull ProgramSelector selector); 85 private native void nativeCancel(long nativeContext); 86 87 private native void nativeCancelAnnouncement(long nativeContext); 88 89 private native RadioManager.ProgramInfo nativeGetProgramInformation(long nativeContext); 90 private native boolean nativeStartBackgroundScan(long nativeContext); 91 private native List<RadioManager.ProgramInfo> nativeGetProgramList(long nativeContext, 92 Map<String, String> vendorFilter); 93 94 private native byte[] nativeGetImage(long nativeContext, int id); 95 96 private native boolean nativeIsAnalogForced(long nativeContext); 97 private native void nativeSetAnalogForced(long nativeContext, boolean isForced); 98 99 private native boolean nativeIsAntennaConnected(long nativeContext); 100 101 @Override 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 113 public boolean isClosed() { 114 return mIsClosed; 115 } 116 117 private void checkNotClosedLocked() { 118 if (mIsClosed) { 119 throw new IllegalStateException("Tuner is closed, no further operations are allowed"); 120 } 121 } 122 123 @Override 124 public void setConfiguration(RadioManager.BandConfig config) { 125 if (config == null) { 126 throw new IllegalArgumentException("The argument must not be a null pointer"); 127 } 128 synchronized (mLock) { 129 checkNotClosedLocked(); 130 nativeSetConfiguration(mNativeContext, config); 131 mRegion = config.getRegion(); 132 } 133 } 134 135 @Override 136 public RadioManager.BandConfig getConfiguration() { 137 synchronized (mLock) { 138 checkNotClosedLocked(); 139 return nativeGetConfiguration(mNativeContext, mRegion); 140 } 141 } 142 143 @Override 144 public void setMuted(boolean mute) { 145 if (!mWithAudio) { 146 throw new IllegalStateException("Can't operate on mute - no audio requested"); 147 } 148 synchronized (mLock) { 149 checkNotClosedLocked(); 150 if (mIsMuted == mute) return; 151 mIsMuted = mute; 152 153 nativeSetMuted(mNativeContext, mute); 154 } 155 } 156 157 @Override 158 public boolean isMuted() { 159 if (!mWithAudio) { 160 Slog.w(TAG, "Tuner did not request audio, pretending it was muted"); 161 return true; 162 } 163 synchronized (mLock) { 164 checkNotClosedLocked(); 165 return mIsMuted; 166 } 167 } 168 169 @Override 170 public void step(boolean directionDown, boolean skipSubChannel) { 171 synchronized (mLock) { 172 checkNotClosedLocked(); 173 nativeStep(mNativeContext, directionDown, skipSubChannel); 174 } 175 } 176 177 @Override 178 public void scan(boolean directionDown, boolean skipSubChannel) { 179 synchronized (mLock) { 180 checkNotClosedLocked(); 181 nativeScan(mNativeContext, directionDown, skipSubChannel); 182 } 183 } 184 185 @Override 186 public void tune(ProgramSelector selector) { 187 if (selector == null) { 188 throw new IllegalArgumentException("The argument must not be a null pointer"); 189 } 190 Slog.i(TAG, "Tuning to " + selector); 191 synchronized (mLock) { 192 checkNotClosedLocked(); 193 nativeTune(mNativeContext, selector); 194 } 195 } 196 197 @Override 198 public void cancel() { 199 synchronized (mLock) { 200 checkNotClosedLocked(); 201 nativeCancel(mNativeContext); 202 } 203 } 204 205 @Override 206 public void cancelAnnouncement() { 207 synchronized (mLock) { 208 checkNotClosedLocked(); 209 nativeCancelAnnouncement(mNativeContext); 210 } 211 } 212 213 @Override 214 public RadioManager.ProgramInfo getProgramInformation() { 215 synchronized (mLock) { 216 checkNotClosedLocked(); 217 return nativeGetProgramInformation(mNativeContext); 218 } 219 } 220 221 @Override 222 public Bitmap getImage(int id) { 223 if (id == 0) { 224 throw new IllegalArgumentException("Image ID is missing"); 225 } 226 227 byte[] rawImage; 228 synchronized (mLock) { 229 rawImage = nativeGetImage(mNativeContext, id); 230 } 231 if (rawImage == null || rawImage.length == 0) { 232 return null; 233 } 234 235 return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length); 236 } 237 238 @Override 239 public boolean startBackgroundScan() { 240 synchronized (mLock) { 241 checkNotClosedLocked(); 242 return nativeStartBackgroundScan(mNativeContext); 243 } 244 } 245 246 @Override 247 public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { 248 Map<String, String> sFilter = vendorFilter; 249 synchronized (mLock) { 250 checkNotClosedLocked(); 251 List<RadioManager.ProgramInfo> list = nativeGetProgramList(mNativeContext, sFilter); 252 if (list == null) { 253 throw new IllegalStateException("Program list is not ready"); 254 } 255 return list; 256 } 257 } 258 259 @Override 260 public boolean isAnalogForced() { 261 synchronized (mLock) { 262 checkNotClosedLocked(); 263 return nativeIsAnalogForced(mNativeContext); 264 } 265 } 266 267 @Override 268 public void setAnalogForced(boolean isForced) { 269 synchronized (mLock) { 270 checkNotClosedLocked(); 271 nativeSetAnalogForced(mNativeContext, isForced); 272 } 273 } 274 275 @Override 276 public boolean isAntennaConnected() { 277 synchronized (mLock) { 278 checkNotClosedLocked(); 279 return nativeIsAntennaConnected(mNativeContext); 280 } 281 } 282} 283