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