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