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 */
16package android.hardware.radio.tests.functional;
17
18import android.Manifest;
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.hardware.radio.ProgramSelector;
22import android.hardware.radio.RadioManager;
23import android.hardware.radio.RadioTuner;
24import android.support.test.InstrumentationRegistry;
25import android.support.test.runner.AndroidJUnit4;
26import android.test.suitebuilder.annotation.MediumTest;
27import android.util.Log;
28
29import java.lang.reflect.Constructor;
30import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.List;
33import java.util.Map;
34
35import org.junit.After;
36import org.junit.Before;
37import org.junit.Test;
38import org.junit.runner.RunWith;
39import org.mockito.ArgumentCaptor;
40import org.mockito.Mock;
41import org.mockito.Mockito;
42import org.mockito.MockitoAnnotations;
43
44import static org.junit.Assert.*;
45import static org.junit.Assume.*;
46import static org.mockito.Matchers.any;
47import static org.mockito.Matchers.anyInt;
48import static org.mockito.Mockito.after;
49import static org.mockito.Mockito.atLeast;
50import static org.mockito.Mockito.atMost;
51import static org.mockito.Mockito.never;
52import static org.mockito.Mockito.timeout;
53import static org.mockito.Mockito.times;
54import static org.mockito.Mockito.verify;
55import static org.mockito.Mockito.verifyNoMoreInteractions;
56import static org.testng.Assert.assertThrows;
57
58/**
59 * A test for broadcast radio API.
60 */
61@RunWith(AndroidJUnit4.class)
62@MediumTest
63public class RadioTunerTest {
64    private static final String TAG = "BroadcastRadioTests.RadioTuner";
65
66    public final Context mContext = InstrumentationRegistry.getContext();
67
68    private final int kConfigCallbackTimeoutMs = 10000;
69    private final int kCancelTimeoutMs = 1000;
70    private final int kTuneCallbackTimeoutMs = 30000;
71    private final int kFullScanTimeoutMs = 60000;
72
73    private RadioManager mRadioManager;
74    private RadioTuner mRadioTuner;
75    private RadioManager.ModuleProperties mModule;
76    private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
77    @Mock private RadioTuner.Callback mCallback;
78
79    RadioManager.AmBandDescriptor mAmBandDescriptor;
80    RadioManager.FmBandDescriptor mFmBandDescriptor;
81
82    RadioManager.BandConfig mAmBandConfig;
83    RadioManager.BandConfig mFmBandConfig;
84
85    @Before
86    public void setup() {
87        MockitoAnnotations.initMocks(this);
88
89        // check if radio is supported and skip the test if it's not
90        PackageManager packageManager = mContext.getPackageManager();
91        boolean isRadioSupported = packageManager.hasSystemFeature(
92                PackageManager.FEATURE_BROADCAST_RADIO);
93        assumeTrue(isRadioSupported);
94
95        // Check radio access permission
96        int res = mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_BROADCAST_RADIO);
97        assertEquals("ACCESS_BROADCAST_RADIO permission not granted",
98                PackageManager.PERMISSION_GRANTED, res);
99
100        mRadioManager = (RadioManager)mContext.getSystemService(Context.RADIO_SERVICE);
101        assertNotNull(mRadioManager);
102
103        int status = mRadioManager.listModules(mModules);
104        assertEquals(RadioManager.STATUS_OK, status);
105        assertFalse(mModules.isEmpty());
106    }
107
108    @After
109    public void tearDown() {
110        mRadioManager = null;
111        mModules.clear();
112        if (mRadioTuner != null) {
113            mRadioTuner.close();
114            mRadioTuner = null;
115        }
116        resetCallback();
117    }
118
119    private void openTuner() {
120        openTuner(true);
121    }
122
123    private void resetCallback() {
124        verify(mCallback, atLeast(0)).onMetadataChanged(any());
125        verifyNoMoreInteractions(mCallback);
126        Mockito.reset(mCallback);
127    }
128
129    private void openTuner(boolean withAudio) {
130        assertNull(mRadioTuner);
131
132        // find FM band and build its config
133        mModule = mModules.get(0);
134
135        for (RadioManager.BandDescriptor band : mModule.getBands()) {
136            Log.d(TAG, "Band: " + band);
137            int bandType = band.getType();
138            if (bandType == RadioManager.BAND_AM || bandType == RadioManager.BAND_AM_HD) {
139                mAmBandDescriptor = (RadioManager.AmBandDescriptor)band;
140            }
141            if (bandType == RadioManager.BAND_FM || bandType == RadioManager.BAND_FM_HD) {
142                mFmBandDescriptor = (RadioManager.FmBandDescriptor)band;
143            }
144        }
145        assertNotNull(mAmBandDescriptor);
146        assertNotNull(mFmBandDescriptor);
147        mAmBandConfig = new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build();
148        mFmBandConfig = new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build();
149
150        mRadioTuner = mRadioManager.openTuner(mModule.getId(),
151                mFmBandConfig, withAudio, mCallback, null);
152        assertNotNull(mRadioTuner);
153        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
154        resetCallback();
155
156        boolean isAntennaConnected = mRadioTuner.isAntennaConnected();
157        assertTrue(isAntennaConnected);
158    }
159
160    @Test
161    public void testOpenTuner() {
162        openTuner();
163    }
164
165    @Test
166    public void testReopenTuner() throws Throwable {
167        openTuner();
168        mRadioTuner.close();
169        mRadioTuner = null;
170        Thread.sleep(100);  // TODO(b/36122635): force reopen
171        openTuner();
172    }
173
174    @Test
175    public void testDoubleClose() {
176        openTuner();
177        mRadioTuner.close();
178        mRadioTuner.close();
179    }
180
181    @Test
182    public void testUseAfterClose() {
183        openTuner();
184        mRadioTuner.close();
185        int ret = mRadioTuner.cancel();
186        assertEquals(RadioManager.STATUS_INVALID_OPERATION, ret);
187    }
188
189    @Test
190    public void testSetAndGetConfiguration() {
191        openTuner();
192
193        // set
194        int ret = mRadioTuner.setConfiguration(mAmBandConfig);
195        assertEquals(RadioManager.STATUS_OK, ret);
196        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
197
198        // get
199        RadioManager.BandConfig[] config = new RadioManager.BandConfig[1];
200        ret = mRadioTuner.getConfiguration(config);
201        assertEquals(RadioManager.STATUS_OK, ret);
202
203        assertEquals(mAmBandConfig, config[0]);
204    }
205
206    @Test
207    public void testSetBadConfiguration() throws Throwable {
208        openTuner();
209
210        // set bad config
211        Constructor<RadioManager.AmBandConfig> configConstr =
212                RadioManager.AmBandConfig.class.getDeclaredConstructor(
213                        int.class, int.class, int.class, int.class, int.class, boolean.class);
214        configConstr.setAccessible(true);
215        RadioManager.AmBandConfig badConfig = configConstr.newInstance(
216                0 /*region*/, RadioManager.BAND_AM /*type*/,
217                10000 /*lowerLimit*/, 1 /*upperLimit*/, 100 /*spacing*/, false /*stereo*/);
218        int ret = mRadioTuner.setConfiguration(badConfig);
219        assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
220        verify(mCallback, never()).onConfigurationChanged(any());
221
222        // set null config
223        ret = mRadioTuner.setConfiguration(null);
224        assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
225        verify(mCallback, never()).onConfigurationChanged(any());
226
227        // setting good config should recover
228        ret = mRadioTuner.setConfiguration(mAmBandConfig);
229        assertEquals(RadioManager.STATUS_OK, ret);
230        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
231    }
232
233    @Test
234    public void testMute() {
235        openTuner();
236
237        boolean isMuted = mRadioTuner.getMute();
238        assertFalse(isMuted);
239
240        int ret = mRadioTuner.setMute(true);
241        assertEquals(RadioManager.STATUS_OK, ret);
242        isMuted = mRadioTuner.getMute();
243        assertTrue(isMuted);
244
245        ret = mRadioTuner.setMute(false);
246        assertEquals(RadioManager.STATUS_OK, ret);
247        isMuted = mRadioTuner.getMute();
248        assertFalse(isMuted);
249    }
250
251    @Test
252    public void testMuteNoAudio() {
253        openTuner(false);
254
255        int ret = mRadioTuner.setMute(false);
256        assertEquals(RadioManager.STATUS_ERROR, ret);
257
258        boolean isMuted = mRadioTuner.getMute();
259        assertTrue(isMuted);
260    }
261
262    @Test
263    public void testStep() {
264        openTuner();
265
266        int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
267        assertEquals(RadioManager.STATUS_OK, ret);
268        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
269
270        resetCallback();
271
272        ret = mRadioTuner.step(RadioTuner.DIRECTION_UP, false);
273        assertEquals(RadioManager.STATUS_OK, ret);
274        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
275    }
276
277    @Test
278    public void testStepLoop() {
279        openTuner();
280
281        for (int i = 0; i < 10; i++) {
282            Log.d(TAG, "step loop iteration " + (i + 1));
283
284            int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
285            assertEquals(RadioManager.STATUS_OK, ret);
286            verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
287
288            resetCallback();
289        }
290    }
291
292    @Test
293    public void testTuneAndGetPI() {
294        openTuner();
295
296        int channel = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing();
297
298        // test tune
299        int ret = mRadioTuner.tune(channel, 0);
300        assertEquals(RadioManager.STATUS_OK, ret);
301        ArgumentCaptor<RadioManager.ProgramInfo> infoc =
302                ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
303        verify(mCallback, timeout(kTuneCallbackTimeoutMs))
304                .onProgramInfoChanged(infoc.capture());
305        assertEquals(channel, infoc.getValue().getChannel());
306
307        // test getProgramInformation
308        RadioManager.ProgramInfo[] info = new RadioManager.ProgramInfo[1];
309        ret = mRadioTuner.getProgramInformation(info);
310        assertEquals(RadioManager.STATUS_OK, ret);
311        assertNotNull(info[0]);
312        assertEquals(channel, info[0].getChannel());
313        Log.d(TAG, "PI: " + info[0].toString());
314    }
315
316    @Test
317    public void testDummyCancel() {
318        openTuner();
319
320        int ret = mRadioTuner.cancel();
321        assertEquals(RadioManager.STATUS_OK, ret);
322    }
323
324    @Test
325    public void testLateCancel() {
326        openTuner();
327
328        int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, false);
329        assertEquals(RadioManager.STATUS_OK, ret);
330        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
331
332        int cancelRet = mRadioTuner.cancel();
333        assertEquals(RadioManager.STATUS_OK, cancelRet);
334    }
335
336    @Test
337    public void testScanAndCancel() {
338        openTuner();
339
340        /* There is a possible race condition between scan and cancel commands - the scan may finish
341         * before cancel command is issued. Thus we accept both outcomes in this test.
342         */
343        int scanRet = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
344        int cancelRet = mRadioTuner.cancel();
345
346        assertEquals(RadioManager.STATUS_OK, scanRet);
347        assertEquals(RadioManager.STATUS_OK, cancelRet);
348
349        verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED);
350        verify(mCallback, atMost(1)).onProgramInfoChanged(any());
351    }
352
353    @Test
354    public void testStartBackgroundScan() {
355        openTuner();
356
357        boolean ret = mRadioTuner.startBackgroundScan();
358        boolean isSupported = mModule.isBackgroundScanningSupported();
359        assertEquals(isSupported, ret);
360    }
361
362    @Test
363    public void testGetProgramList() {
364        openTuner();
365
366        try {
367            Map<String, String> filter = new HashMap<>();
368            filter.put("com.google.dummy", "dummy");
369            List<RadioManager.ProgramInfo> list = mRadioTuner.getProgramList(filter);
370            assertNotNull(list);
371        } catch (IllegalStateException e) {
372            // the list may or may not be ready at this point
373            Log.i(TAG, "Background list is not ready");
374        }
375    }
376
377    @Test
378    public void testTuneFromProgramList() {
379        openTuner();
380
381        List<RadioManager.ProgramInfo> list;
382
383        try {
384            list = mRadioTuner.getProgramList(null);
385            assertNotNull(list);
386        } catch (IllegalStateException e) {
387            Log.i(TAG, "Background list is not ready, trying to fix it");
388
389            boolean success = mRadioTuner.startBackgroundScan();
390            assertTrue(success);
391            verify(mCallback, timeout(kFullScanTimeoutMs)).onBackgroundScanComplete();
392
393            list = mRadioTuner.getProgramList(null);
394            assertNotNull(list);
395        }
396
397        if (list.isEmpty()) {
398            Log.i(TAG, "Program list is empty, can't test tune");
399            return;
400        }
401
402        ProgramSelector sel = list.get(0).getSelector();
403        mRadioTuner.tune(sel);
404        ArgumentCaptor<RadioManager.ProgramInfo> infoc =
405                ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
406        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(infoc.capture());
407        assertEquals(sel, infoc.getValue().getSelector());
408    }
409
410    @Test
411    public void testForcedAnalog() {
412        openTuner();
413
414        boolean isSupported = true;
415        boolean isForced;
416        try {
417            isForced = mRadioTuner.isAnalogForced();
418            assertFalse(isForced);
419        } catch (IllegalStateException ex) {
420            Log.i(TAG, "Forced analog switch is not supported by this tuner");
421            isSupported = false;
422        }
423
424        if (isSupported) {
425            mRadioTuner.setAnalogForced(true);
426            isForced = mRadioTuner.isAnalogForced();
427            assertTrue(isForced);
428
429            mRadioTuner.setAnalogForced(false);
430            isForced = mRadioTuner.isAnalogForced();
431            assertFalse(isForced);
432        } else {
433            assertThrows(IllegalStateException.class, () -> mRadioTuner.setAnalogForced(true));
434        }
435    }
436}
437