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.hal1;
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.ProgramList;
25import android.hardware.radio.ProgramSelector;
26import android.hardware.radio.RadioManager;
27import android.os.IBinder;
28import android.os.RemoteException;
29import android.util.Slog;
30
31import java.util.Collections;
32import java.util.List;
33import java.util.Map;
34import java.util.Objects;
35
36class 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
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
70    protected void finalize() throws Throwable {
71        nativeFinalize(mNativeContext);
72        super.finalize();
73    }
74
75    private native long nativeInit(int halRev, boolean withAudio, int band);
76    private native void nativeFinalize(long nativeContext);
77    private native void nativeClose(long nativeContext);
78
79    private native void nativeSetConfiguration(long nativeContext,
80            @NonNull RadioManager.BandConfig config);
81    private native RadioManager.BandConfig nativeGetConfiguration(long nativeContext, int region);
82
83    private native void nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel);
84    private native void nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel);
85    private native void nativeTune(long nativeContext, @NonNull ProgramSelector selector);
86    private native void nativeCancel(long nativeContext);
87
88    private native void nativeCancelAnnouncement(long nativeContext);
89
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    @Override
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
111    public boolean isClosed() {
112        return mIsClosed;
113    }
114
115    private void checkNotClosedLocked() {
116        if (mIsClosed) {
117            throw new IllegalStateException("Tuner is closed, no further operations are allowed");
118        }
119    }
120
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
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
140    public RadioManager.BandConfig getConfiguration() {
141        synchronized (mLock) {
142            checkNotClosedLocked();
143            return nativeGetConfiguration(mNativeContext, mRegion);
144        }
145    }
146
147    @Override
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
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
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
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
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
204    public void cancel() {
205        synchronized (mLock) {
206            checkNotClosedLocked();
207            nativeCancel(mNativeContext);
208        }
209    }
210
211    @Override
212    public void cancelAnnouncement() {
213        synchronized (mLock) {
214            checkNotClosedLocked();
215            nativeCancelAnnouncement(mNativeContext);
216        }
217    }
218
219    @Override
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
237    public boolean startBackgroundScan() {
238        synchronized (mLock) {
239            checkNotClosedLocked();
240            return nativeStartBackgroundScan(mNativeContext);
241        }
242    }
243
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
257    public void startProgramListUpdates(ProgramList.Filter filter) {
258        mTunerCallback.startProgramListUpdates(filter);
259    }
260
261    @Override
262    public void stopProgramListUpdates() {
263        mTunerCallback.stopProgramListUpdates();
264    }
265
266    @Override
267    public boolean isConfigFlagSupported(int flag) {
268        return flag == RadioManager.CONFIG_FORCE_ANALOG;
269    }
270
271    @Override
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
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
295    public Map setParameters(Map parameters) {
296        throw new UnsupportedOperationException("Not supported by HAL 1.x");
297    }
298
299    @Override
300    public Map getParameters(List<String> keys) {
301        throw new UnsupportedOperationException("Not supported by HAL 1.x");
302    }
303}
304