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