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 android.hardware.radio;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.graphics.Bitmap;
22import android.os.RemoteException;
23import android.util.Log;
24
25import java.util.List;
26import java.util.Map;
27import java.util.Objects;
28
29/**
30 * Implements the RadioTuner interface by forwarding calls to radio service.
31 */
32class TunerAdapter extends RadioTuner {
33    private static final String TAG = "BroadcastRadio.TunerAdapter";
34
35    @NonNull private final ITuner mTuner;
36    @NonNull private final TunerCallbackAdapter mCallback;
37    private boolean mIsClosed = false;
38
39    private @RadioManager.Band int mBand;
40
41    private ProgramList mLegacyListProxy;
42    private Map<String, String> mLegacyListFilter;
43
44    TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback,
45            @RadioManager.Band int band) {
46        mTuner = Objects.requireNonNull(tuner);
47        mCallback = Objects.requireNonNull(callback);
48        mBand = band;
49    }
50
51    @Override
52    public void close() {
53        synchronized (mTuner) {
54            if (mIsClosed) {
55                Log.v(TAG, "Tuner is already closed");
56                return;
57            }
58            mIsClosed = true;
59            if (mLegacyListProxy != null) {
60                mLegacyListProxy.close();
61                mLegacyListProxy = null;
62            }
63            mCallback.close();
64        }
65        try {
66            mTuner.close();
67        } catch (RemoteException e) {
68            Log.e(TAG, "Exception trying to close tuner", e);
69        }
70    }
71
72    @Override
73    public int setConfiguration(RadioManager.BandConfig config) {
74        if (config == null) return RadioManager.STATUS_BAD_VALUE;
75        try {
76            mTuner.setConfiguration(config);
77            mBand = config.getType();
78            return RadioManager.STATUS_OK;
79        } catch (IllegalArgumentException e) {
80            Log.e(TAG, "Can't set configuration", e);
81            return RadioManager.STATUS_BAD_VALUE;
82        } catch (RemoteException e) {
83            Log.e(TAG, "service died", e);
84            return RadioManager.STATUS_DEAD_OBJECT;
85        }
86    }
87
88    @Override
89    public int getConfiguration(RadioManager.BandConfig[] config) {
90        if (config == null || config.length != 1) {
91            throw new IllegalArgumentException("The argument must be an array of length 1");
92        }
93        try {
94            config[0] = mTuner.getConfiguration();
95            return RadioManager.STATUS_OK;
96        } catch (RemoteException e) {
97            Log.e(TAG, "service died", e);
98            return RadioManager.STATUS_DEAD_OBJECT;
99        }
100    }
101
102    @Override
103    public int setMute(boolean mute) {
104        try {
105            mTuner.setMuted(mute);
106        } catch (IllegalStateException e) {
107            Log.e(TAG, "Can't set muted", e);
108            return RadioManager.STATUS_ERROR;
109        } catch (RemoteException e) {
110            Log.e(TAG, "service died", e);
111            return RadioManager.STATUS_DEAD_OBJECT;
112        }
113        return RadioManager.STATUS_OK;
114    }
115
116    @Override
117    public boolean getMute() {
118        try {
119            return mTuner.isMuted();
120        } catch (RemoteException e) {
121            Log.e(TAG, "service died", e);
122            return true;
123        }
124    }
125
126    @Override
127    public int step(int direction, boolean skipSubChannel) {
128        try {
129            mTuner.step(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
130        } catch (IllegalStateException e) {
131            Log.e(TAG, "Can't step", e);
132            return RadioManager.STATUS_INVALID_OPERATION;
133        } catch (RemoteException e) {
134            Log.e(TAG, "service died", e);
135            return RadioManager.STATUS_DEAD_OBJECT;
136        }
137        return RadioManager.STATUS_OK;
138    }
139
140    @Override
141    public int scan(int direction, boolean skipSubChannel) {
142        try {
143            mTuner.scan(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
144        } catch (IllegalStateException e) {
145            Log.e(TAG, "Can't scan", e);
146            return RadioManager.STATUS_INVALID_OPERATION;
147        } catch (RemoteException e) {
148            Log.e(TAG, "service died", e);
149            return RadioManager.STATUS_DEAD_OBJECT;
150        }
151        return RadioManager.STATUS_OK;
152    }
153
154    @Override
155    public int tune(int channel, int subChannel) {
156        try {
157            mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel));
158        } catch (IllegalStateException e) {
159            Log.e(TAG, "Can't tune", e);
160            return RadioManager.STATUS_INVALID_OPERATION;
161        } catch (IllegalArgumentException e) {
162            Log.e(TAG, "Can't tune", e);
163            return RadioManager.STATUS_BAD_VALUE;
164        } catch (RemoteException e) {
165            Log.e(TAG, "service died", e);
166            return RadioManager.STATUS_DEAD_OBJECT;
167        }
168        return RadioManager.STATUS_OK;
169    }
170
171    @Override
172    public void tune(@NonNull ProgramSelector selector) {
173        try {
174            mTuner.tune(selector);
175        } catch (RemoteException e) {
176            throw new RuntimeException("service died", e);
177        }
178    }
179
180    @Override
181    public int cancel() {
182        try {
183            mTuner.cancel();
184        } catch (IllegalStateException e) {
185            Log.e(TAG, "Can't cancel", e);
186            return RadioManager.STATUS_INVALID_OPERATION;
187        } catch (RemoteException e) {
188            Log.e(TAG, "service died", e);
189            return RadioManager.STATUS_DEAD_OBJECT;
190        }
191        return RadioManager.STATUS_OK;
192    }
193
194    @Override
195    public void cancelAnnouncement() {
196        try {
197            mTuner.cancelAnnouncement();
198        } catch (RemoteException e) {
199            throw new RuntimeException("service died", e);
200        }
201    }
202
203    @Override
204    public int getProgramInformation(RadioManager.ProgramInfo[] info) {
205        if (info == null || info.length != 1) {
206            Log.e(TAG, "The argument must be an array of length 1");
207            return RadioManager.STATUS_BAD_VALUE;
208        }
209
210        RadioManager.ProgramInfo current = mCallback.getCurrentProgramInformation();
211        if (current == null) {
212            Log.w(TAG, "Didn't get program info yet");
213            return RadioManager.STATUS_INVALID_OPERATION;
214        }
215        info[0] = current;
216        return RadioManager.STATUS_OK;
217    }
218
219    @Override
220    public @Nullable Bitmap getMetadataImage(int id) {
221        try {
222            return mTuner.getImage(id);
223        } catch (RemoteException e) {
224            throw new RuntimeException("service died", e);
225        }
226    }
227
228    @Override
229    public boolean startBackgroundScan() {
230        try {
231            return mTuner.startBackgroundScan();
232        } catch (RemoteException e) {
233            throw new RuntimeException("service died", e);
234        }
235    }
236
237    @Override
238    public @NonNull List<RadioManager.ProgramInfo>
239            getProgramList(@Nullable Map<String, String> vendorFilter) {
240        synchronized (mTuner) {
241            if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) {
242                Log.i(TAG, "Program list filter has changed, requesting new list");
243                mLegacyListProxy = new ProgramList();
244                mLegacyListFilter = vendorFilter;
245
246                mCallback.clearLastCompleteList();
247                mCallback.setProgramListObserver(mLegacyListProxy, () -> { });
248                try {
249                    mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
250                } catch (RemoteException ex) {
251                    throw new RuntimeException("service died", ex);
252                }
253            }
254
255            List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
256            if (list == null) throw new IllegalStateException("Program list is not ready yet");
257            return list;
258        }
259    }
260
261    @Override
262    public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
263        synchronized (mTuner) {
264            if (mLegacyListProxy != null) {
265                mLegacyListProxy.close();
266                mLegacyListProxy = null;
267            }
268            mLegacyListFilter = null;
269
270            ProgramList list = new ProgramList();
271            mCallback.setProgramListObserver(list, () -> {
272                try {
273                    mTuner.stopProgramListUpdates();
274                } catch (RemoteException ex) {
275                    Log.e(TAG, "Couldn't stop program list updates", ex);
276                }
277            });
278
279            try {
280                mTuner.startProgramListUpdates(filter);
281            } catch (UnsupportedOperationException ex) {
282                Log.i(TAG, "Program list is not supported with this hardware");
283                return null;
284            } catch (RemoteException ex) {
285                mCallback.setProgramListObserver(null, () -> { });
286                throw new RuntimeException("service died", ex);
287            }
288
289            return list;
290        }
291    }
292
293    @Override
294    public boolean isAnalogForced() {
295        try {
296            return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG);
297        } catch (UnsupportedOperationException ex) {
298            throw new IllegalStateException(ex);
299        }
300    }
301
302    @Override
303    public void setAnalogForced(boolean isForced) {
304        try {
305            setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced);
306        } catch (UnsupportedOperationException ex) {
307            throw new IllegalStateException(ex);
308        }
309    }
310
311    @Override
312    public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
313        try {
314            return mTuner.isConfigFlagSupported(flag);
315        } catch (RemoteException e) {
316            throw new RuntimeException("service died", e);
317        }
318    }
319
320    @Override
321    public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
322        try {
323            return mTuner.isConfigFlagSet(flag);
324        } catch (RemoteException e) {
325            throw new RuntimeException("service died", e);
326        }
327    }
328
329    @Override
330    public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
331        try {
332            mTuner.setConfigFlag(flag, value);
333        } catch (RemoteException e) {
334            throw new RuntimeException("service died", e);
335        }
336    }
337
338    @Override
339    public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) {
340        try {
341            return mTuner.setParameters(Objects.requireNonNull(parameters));
342        } catch (RemoteException e) {
343            throw new RuntimeException("service died", e);
344        }
345    }
346
347    @Override
348    public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) {
349        try {
350            return mTuner.getParameters(Objects.requireNonNull(keys));
351        } catch (RemoteException e) {
352            throw new RuntimeException("service died", e);
353        }
354    }
355
356    @Override
357    public boolean isAntennaConnected() {
358        return mCallback.isAntennaConnected();
359    }
360
361    @Override
362    public boolean hasControl() {
363        try {
364            // don't rely on mIsClosed, as tuner might get closed internally
365            return !mTuner.isClosed();
366        } catch (RemoteException e) {
367            return false;
368        }
369    }
370}
371