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