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        verify(mCallback, atLeast(0)).onProgramInfoChanged(any());
126        verify(mCallback, atLeast(0)).onProgramListChanged();
127        verifyNoMoreInteractions(mCallback);
128        Mockito.reset(mCallback);
129    }
130
131    private void openTuner(boolean withAudio) {
132        assertNull(mRadioTuner);
133
134        // find FM band and build its config
135        mModule = mModules.get(0);
136
137        for (RadioManager.BandDescriptor band : mModule.getBands()) {
138            Log.d(TAG, "Band: " + band);
139            int bandType = band.getType();
140            if (bandType == RadioManager.BAND_AM || bandType == RadioManager.BAND_AM_HD) {
141                mAmBandDescriptor = (RadioManager.AmBandDescriptor)band;
142            }
143            if (bandType == RadioManager.BAND_FM || bandType == RadioManager.BAND_FM_HD) {
144                mFmBandDescriptor = (RadioManager.FmBandDescriptor)band;
145            }
146        }
147        assertNotNull(mAmBandDescriptor);
148        assertNotNull(mFmBandDescriptor);
149        mAmBandConfig = new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build();
150        mFmBandConfig = new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build();
151
152        mRadioTuner = mRadioManager.openTuner(mModule.getId(),
153                mFmBandConfig, withAudio, mCallback, null);
154        if (!withAudio) {
155            // non-audio sessions might not be supported - if so, then skip the test
156            assumeNotNull(mRadioTuner);
157        }
158        assertNotNull(mRadioTuner);
159        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
160        resetCallback();
161
162        boolean isAntennaConnected = mRadioTuner.isAntennaConnected();
163        assertTrue(isAntennaConnected);
164    }
165
166    @Test
167    public void testOpenTuner() {
168        openTuner();
169    }
170
171    @Test
172    public void testReopenTuner() throws Throwable {
173        openTuner();
174        mRadioTuner.close();
175        mRadioTuner = null;
176        Thread.sleep(100);  // TODO(b/36122635): force reopen
177        openTuner();
178    }
179
180    @Test
181    public void testDoubleClose() {
182        openTuner();
183        mRadioTuner.close();
184        mRadioTuner.close();
185    }
186
187    @Test
188    public void testUseAfterClose() {
189        openTuner();
190        mRadioTuner.close();
191        int ret = mRadioTuner.cancel();
192        assertEquals(RadioManager.STATUS_INVALID_OPERATION, ret);
193    }
194
195    @Test
196    public void testSetAndGetConfiguration() {
197        openTuner();
198
199        // set
200        int ret = mRadioTuner.setConfiguration(mAmBandConfig);
201        assertEquals(RadioManager.STATUS_OK, ret);
202        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
203
204        // get
205        RadioManager.BandConfig[] config = new RadioManager.BandConfig[1];
206        ret = mRadioTuner.getConfiguration(config);
207        assertEquals(RadioManager.STATUS_OK, ret);
208
209        assertEquals(mAmBandConfig, config[0]);
210    }
211
212    @Test
213    public void testSetBadConfiguration() throws Throwable {
214        openTuner();
215
216        // set null config
217        int ret = mRadioTuner.setConfiguration(null);
218        assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
219        verify(mCallback, never()).onConfigurationChanged(any());
220
221        // setting good config should recover
222        ret = mRadioTuner.setConfiguration(mAmBandConfig);
223        assertEquals(RadioManager.STATUS_OK, ret);
224        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
225    }
226
227    @Test
228    public void testMute() {
229        openTuner();
230
231        boolean isMuted = mRadioTuner.getMute();
232        assertFalse(isMuted);
233
234        int ret = mRadioTuner.setMute(true);
235        assertEquals(RadioManager.STATUS_OK, ret);
236        isMuted = mRadioTuner.getMute();
237        assertTrue(isMuted);
238
239        ret = mRadioTuner.setMute(false);
240        assertEquals(RadioManager.STATUS_OK, ret);
241        isMuted = mRadioTuner.getMute();
242        assertFalse(isMuted);
243    }
244
245    @Test
246    public void testMuteNoAudio() {
247        openTuner(false);
248
249        int ret = mRadioTuner.setMute(false);
250        assertEquals(RadioManager.STATUS_ERROR, ret);
251
252        boolean isMuted = mRadioTuner.getMute();
253        assertTrue(isMuted);
254    }
255
256    @Test
257    public void testStep() {
258        openTuner();
259
260        int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
261        assertEquals(RadioManager.STATUS_OK, ret);
262        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
263
264        resetCallback();
265
266        ret = mRadioTuner.step(RadioTuner.DIRECTION_UP, false);
267        assertEquals(RadioManager.STATUS_OK, ret);
268        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
269    }
270
271    @Test
272    public void testStepLoop() {
273        openTuner();
274
275        for (int i = 0; i < 10; i++) {
276            Log.d(TAG, "step loop iteration " + (i + 1));
277
278            int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
279            assertEquals(RadioManager.STATUS_OK, ret);
280            verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
281
282            resetCallback();
283        }
284    }
285
286    @Test
287    public void testTuneAndGetPI() {
288        openTuner();
289
290        int channel = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing();
291
292        // test tune
293        int ret = mRadioTuner.tune(channel, 0);
294        assertEquals(RadioManager.STATUS_OK, ret);
295        ArgumentCaptor<RadioManager.ProgramInfo> infoc =
296                ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
297        verify(mCallback, timeout(kTuneCallbackTimeoutMs))
298                .onProgramInfoChanged(infoc.capture());
299        assertEquals(channel, infoc.getValue().getChannel());
300
301        // test getProgramInformation
302        RadioManager.ProgramInfo[] info = new RadioManager.ProgramInfo[1];
303        ret = mRadioTuner.getProgramInformation(info);
304        assertEquals(RadioManager.STATUS_OK, ret);
305        assertNotNull(info[0]);
306        assertEquals(channel, info[0].getChannel());
307        Log.d(TAG, "PI: " + info[0].toString());
308    }
309
310    @Test
311    public void testDummyCancel() {
312        openTuner();
313
314        int ret = mRadioTuner.cancel();
315        assertEquals(RadioManager.STATUS_OK, ret);
316    }
317
318    @Test
319    public void testLateCancel() {
320        openTuner();
321
322        int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, false);
323        assertEquals(RadioManager.STATUS_OK, ret);
324        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
325
326        int cancelRet = mRadioTuner.cancel();
327        assertEquals(RadioManager.STATUS_OK, cancelRet);
328    }
329
330    @Test
331    public void testScanAndCancel() {
332        openTuner();
333
334        /* There is a possible race condition between scan and cancel commands - the scan may finish
335         * before cancel command is issued. Thus we accept both outcomes in this test.
336         */
337        int scanRet = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
338        int cancelRet = mRadioTuner.cancel();
339
340        assertEquals(RadioManager.STATUS_OK, scanRet);
341        assertEquals(RadioManager.STATUS_OK, cancelRet);
342
343        verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED);
344        verify(mCallback, atMost(1)).onProgramInfoChanged(any());
345    }
346
347    @Test
348    public void testStartBackgroundScan() {
349        openTuner();
350
351        boolean ret = mRadioTuner.startBackgroundScan();
352        boolean isSupported = mModule.isBackgroundScanningSupported();
353        assertEquals(isSupported, ret);
354    }
355
356    @Test
357    public void testGetProgramList() {
358        openTuner();
359
360        try {
361            Map<String, String> filter = new HashMap<>();
362            filter.put("com.google.dummy", "dummy");
363            List<RadioManager.ProgramInfo> list = mRadioTuner.getProgramList(filter);
364            assertNotNull(list);
365        } catch (IllegalStateException e) {
366            // the list may or may not be ready at this point
367            Log.i(TAG, "Background list is not ready");
368        }
369    }
370
371    @Test
372    public void testTuneFromProgramList() {
373        openTuner();
374
375        List<RadioManager.ProgramInfo> list;
376
377        try {
378            list = mRadioTuner.getProgramList(null);
379            assertNotNull(list);
380        } catch (IllegalStateException e) {
381            Log.i(TAG, "Background list is not ready, trying to fix it");
382
383            boolean success = mRadioTuner.startBackgroundScan();
384            assertTrue(success);
385            verify(mCallback, timeout(kFullScanTimeoutMs)).onBackgroundScanComplete();
386
387            list = mRadioTuner.getProgramList(null);
388            assertNotNull(list);
389        }
390
391        if (list.isEmpty()) {
392            Log.i(TAG, "Program list is empty, can't test tune");
393            return;
394        }
395
396        ProgramSelector sel = list.get(0).getSelector();
397        mRadioTuner.tune(sel);
398        ArgumentCaptor<RadioManager.ProgramInfo> infoc =
399                ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
400        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(infoc.capture());
401        assertEquals(sel, infoc.getValue().getSelector());
402    }
403
404    @Test
405    public void testForcedAnalog() {
406        openTuner();
407
408        boolean isSupported = true;
409        boolean isForced;
410        try {
411            isForced = mRadioTuner.isAnalogForced();
412            assertFalse(isForced);
413        } catch (IllegalStateException ex) {
414            Log.i(TAG, "Forced analog switch is not supported by this tuner");
415            isSupported = false;
416        }
417
418        if (isSupported) {
419            mRadioTuner.setAnalogForced(true);
420            isForced = mRadioTuner.isAnalogForced();
421            assertTrue(isForced);
422
423            mRadioTuner.setAnalogForced(false);
424            isForced = mRadioTuner.isAnalogForced();
425            assertFalse(isForced);
426        } else {
427            assertThrows(IllegalStateException.class, () -> mRadioTuner.setAnalogForced(true));
428        }
429    }
430}
431