1 /* 2 * Copyright (C) 2022 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 17 package android.broadcastradio.cts; 18 19 import static com.google.common.truth.TruthJUnit.assume; 20 21 import static org.junit.Assert.assertThrows; 22 import static org.junit.Assume.assumeFalse; 23 import static org.junit.Assume.assumeTrue; 24 25 import android.graphics.Bitmap; 26 import android.hardware.radio.Flags; 27 import android.hardware.radio.ProgramList; 28 import android.hardware.radio.ProgramSelector; 29 import android.hardware.radio.RadioAlert; 30 import android.hardware.radio.RadioManager; 31 import android.hardware.radio.RadioMetadata; 32 import android.hardware.radio.RadioTuner; 33 import android.platform.test.annotations.RequiresFlagsEnabled; 34 35 import androidx.test.ext.junit.runners.AndroidJUnit4; 36 37 import com.android.compatibility.common.util.ApiTest; 38 39 import com.google.common.collect.Range; 40 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import java.util.Collections; 45 import java.util.List; 46 import java.util.Set; 47 import java.util.concurrent.CountDownLatch; 48 import java.util.concurrent.TimeUnit; 49 50 /** 51 * CTS test for broadcast radio. 52 */ 53 @RunWith(AndroidJUnit4.class) 54 public final class RadioTunerTest extends AbstractRadioTestCase { 55 56 private static final int TEST_CONFIG_FLAG = RadioManager.CONFIG_FORCE_ANALOG; 57 58 @Test 59 @ApiTest(apis = {"android.hardware.radio.RadioTuner#close"}) close_twice()60 public void close_twice() throws Exception { 61 openAmFmTuner(); 62 63 mRadioTuner.close(); 64 mRadioTuner.close(); 65 66 mExpect.withMessage("Error callbacks").that(mCallback.errorCount).isEqualTo(0); 67 } 68 69 @Test 70 @ApiTest(apis = {"android.hardware.radio.RadioTuner#cancel", 71 "android.hardware.radio.RadioTuner#close"}) cancel_afterTunerCloses_returnsInvalidOperationStatus()72 public void cancel_afterTunerCloses_returnsInvalidOperationStatus() throws Exception { 73 openAmFmTuner(); 74 mRadioTuner.close(); 75 76 int cancelResult = mRadioTuner.cancel(); 77 78 mExpect.withMessage("Result of cancel operation after tuner closed") 79 .that(cancelResult).isEqualTo(RadioManager.STATUS_INVALID_OPERATION); 80 } 81 82 @Test 83 @ApiTest(apis = {"android.hardware.radio.RadioTuner#getMute"}) getMute_returnsFalse()84 public void getMute_returnsFalse() throws Exception { 85 openAmFmTuner(); 86 87 boolean isMuted = mRadioTuner.getMute(); 88 89 mExpect.withMessage("Mute status of tuner without audio").that(isMuted).isFalse(); 90 } 91 92 @Test 93 @ApiTest(apis = {"android.hardware.radio.RadioTuner#getMute", 94 "android.hardware.radio.RadioTuner#setMute"}) setMute_withTrue_MuteSucceeds()95 public void setMute_withTrue_MuteSucceeds() throws Exception { 96 openAmFmTuner(); 97 98 int result = mRadioTuner.setMute(true); 99 100 mExpect.withMessage("Result of setting mute with true") 101 .that(result).isEqualTo(RadioManager.STATUS_OK); 102 mExpect.withMessage("Mute status after setting mute with true") 103 .that(mRadioTuner.getMute()).isTrue(); 104 } 105 106 @Test 107 @ApiTest(apis = {"android.hardware.radio.RadioTuner#getMute", 108 "android.hardware.radio.RadioTuner#setMute"}) setMute_withFalse_MuteSucceeds()109 public void setMute_withFalse_MuteSucceeds() throws Exception { 110 openAmFmTuner(); 111 mRadioTuner.setMute(true); 112 113 int result = mRadioTuner.setMute(false); 114 115 mExpect.withMessage("Result of setting mute with false") 116 .that(result).isEqualTo(RadioManager.STATUS_OK); 117 mExpect.withMessage("Mute status after setting mute with false") 118 .that(mRadioTuner.getMute()).isFalse(); 119 } 120 121 @Test 122 @ApiTest(apis = {"android.hardware.radio.RadioTuner#setMute"}) setMute_withTunerWithoutAudio_returnsError()123 public void setMute_withTunerWithoutAudio_returnsError() throws Exception { 124 openAmFmTuner(/* withAudio= */ false); 125 126 int result = mRadioTuner.setMute(false); 127 128 mExpect.withMessage("Status of setting mute on tuner without audio") 129 .that(result).isEqualTo(RadioManager.STATUS_ERROR); 130 } 131 132 @Test 133 @ApiTest(apis = {"android.hardware.radio.RadioTuner#getMute"}) isMuted_withTunerWithoutAudio_returnsTrue()134 public void isMuted_withTunerWithoutAudio_returnsTrue() throws Exception { 135 openAmFmTuner(/* withAudio= */ false); 136 137 boolean isMuted = mRadioTuner.getMute(); 138 139 mExpect.withMessage("Mute status of tuner without audio").that(isMuted).isTrue(); 140 } 141 142 @Test 143 @ApiTest(apis = {"android.hardware.radio.RadioTuner#step"}) step_withDownDirection()144 public void step_withDownDirection() throws Exception { 145 openAmFmTuner(); 146 147 int stepResult = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ true); 148 149 mExpect.withMessage("Program info callback invoked for step operation with down direction") 150 .that(mCallback.waitForProgramInfoChangeCallback(TUNE_CALLBACK_TIMEOUT_MS)) 151 .isTrue(); 152 mExpect.withMessage("Result of step operation with down direction") 153 .that(stepResult).isEqualTo(RadioManager.STATUS_OK); 154 } 155 156 @Test 157 @ApiTest(apis = {"android.hardware.radio.RadioTuner#step", 158 "android.hardware.radio.RadioTuner.Callback#onProgramInfoChanged"}) step_withUpDirection()159 public void step_withUpDirection() throws Exception { 160 openAmFmTuner(); 161 162 int stepResult = mRadioTuner.step(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false); 163 164 mExpect.withMessage("Program info callback for step operation with up direction") 165 .that(mCallback.waitForProgramInfoChangeCallback(TUNE_CALLBACK_TIMEOUT_MS)) 166 .isTrue(); 167 mExpect.withMessage("Result of step operation with up direction") 168 .that(stepResult).isEqualTo(RadioManager.STATUS_OK); 169 } 170 171 @Test 172 @ApiTest(apis = {"android.hardware.radio.RadioTuner#step", 173 "android.hardware.radio.RadioTuner.Callback#onProgramInfoChanged"}) step_withMultipleTimes()174 public void step_withMultipleTimes() throws Exception { 175 openAmFmTuner(); 176 177 for (int index = 0; index < 10; index++) { 178 int stepResult = 179 mRadioTuner.step(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ true); 180 181 mExpect.withMessage("Program info callback for step operation at iteration %s", 182 index).that(mCallback.waitForProgramInfoChangeCallback( 183 TUNE_CALLBACK_TIMEOUT_MS)).isTrue(); 184 mExpect.withMessage("Result of step operation at iteration %s", index) 185 .that(stepResult).isEqualTo(RadioManager.STATUS_OK); 186 187 assertNoTunerFailureAndResetCallback("step"); 188 } 189 } 190 191 @Test 192 @ApiTest(apis = {"android.hardware.radio.RadioTuner#seek"}) seek_onProgramInfoChangedInvoked()193 public void seek_onProgramInfoChangedInvoked() throws Exception { 194 openAmFmTuner(); 195 196 int seekResult = mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true); 197 198 mExpect.withMessage("Program info callback for seek operation") 199 .that(mCallback.waitForProgramInfoChangeCallback(TUNE_CALLBACK_TIMEOUT_MS)) 200 .isTrue(); 201 mExpect.withMessage("Result of seek operation") 202 .that(seekResult).isEqualTo(RadioManager.STATUS_OK); 203 } 204 205 @Test 206 @ApiTest(apis = {"android.hardware.radio.RadioTuner#tune"}) tune_withFmSelector_onProgramInfoChangedInvoked()207 public void tune_withFmSelector_onProgramInfoChangedInvoked() throws Exception { 208 openAmFmTuner(); 209 int freq = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing(); 210 ProgramSelector sel = ProgramSelector.createAmFmSelector(RadioManager.BAND_FM, freq, 211 /* subChannel= */ 0); 212 213 mRadioTuner.tune(sel); 214 215 mExpect.withMessage("Program info callback for tune operation") 216 .that(mCallback.waitForProgramInfoChangeCallback(TUNE_CALLBACK_TIMEOUT_MS)) 217 .isTrue(); 218 mExpect.withMessage("Program selector tuned to") 219 .that(mCallback.currentProgramInfo.getSelector()).isEqualTo(sel); 220 } 221 222 @Test 223 @ApiTest(apis = {"android.hardware.radio.RadioTuner#cancel"}) cancel_afterNoOperations()224 public void cancel_afterNoOperations() throws Exception { 225 openAmFmTuner(); 226 227 int cancelResult = mRadioTuner.cancel(); 228 229 mCallback.waitForProgramInfoChangeCallback(CANCEL_TIMEOUT_MS); 230 mExpect.withMessage("Result of cancel operation after no operations") 231 .that(cancelResult).isEqualTo(RadioManager.STATUS_OK); 232 mExpect.withMessage("Tuner failure of cancel operation after no operations") 233 .that(mCallback.tunerFailureResult).isNotEqualTo(RadioTuner.TUNER_RESULT_CANCELED); 234 } 235 236 @Test 237 @ApiTest(apis = {"android.hardware.radio.RadioTuner#step", 238 "android.hardware.radio.RadioTuner#cancel"}) cancel_afterStepCompletes()239 public void cancel_afterStepCompletes() throws Exception { 240 openAmFmTuner(); 241 int stepResult = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false); 242 mExpect.withMessage("Result of step operation") 243 .that(stepResult).isEqualTo(RadioManager.STATUS_OK); 244 mExpect.withMessage("Program info callback before cancellation") 245 .that(mCallback.waitForProgramInfoChangeCallback(TUNE_CALLBACK_TIMEOUT_MS)) 246 .isTrue(); 247 248 int cancelResult = mRadioTuner.cancel(); 249 250 mCallback.waitForProgramInfoChangeCallback(CANCEL_TIMEOUT_MS); 251 mExpect.withMessage("Result of cancel operation after step completed") 252 .that(cancelResult).isEqualTo(RadioManager.STATUS_OK); 253 mExpect.withMessage("Tuner failure of cancel operation after step completed") 254 .that(mCallback.tunerFailureResult).isNotEqualTo(RadioTuner.TUNER_RESULT_CANCELED); 255 } 256 257 @Test 258 @ApiTest(apis = {"android.hardware.radio.RadioTuner#getDynamicProgramList"}) getDynamicProgramList_programListUpdated()259 public void getDynamicProgramList_programListUpdated() throws Exception { 260 openAmFmTuner(); 261 TestOnCompleteListener completeListener = new TestOnCompleteListener(); 262 263 ProgramList list = assumeNonNullProgramList(); 264 try { 265 list.addOnCompleteListener(completeListener); 266 267 mExpect.withMessage("List update completion").that(completeListener.waitForCallback()) 268 .isTrue(); 269 } finally { 270 list.close(); 271 } 272 } 273 274 @Test 275 @ApiTest(apis = {"android.hardware.radio.ProgramList#getProgramInfos"}) 276 @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) getProgramInfos_fromDynamicProgramList()277 public void getProgramInfos_fromDynamicProgramList() throws Exception { 278 openAmFmTuner(); 279 TestOnCompleteListener completeListener = new TestOnCompleteListener(); 280 ProgramList list = assumeNonNullProgramList(); 281 try { 282 list.addOnCompleteListener(completeListener); 283 mExpect.withMessage("List update completion before getting program info") 284 .that(completeListener.waitForCallback()).isTrue(); 285 List<RadioManager.ProgramInfo> programInfoList = list.toList(); 286 assume().withMessage("Non-empty program program list") 287 .that(programInfoList).isNotEmpty(); 288 RadioManager.ProgramInfo firstProgram = programInfoList.get(0); 289 290 mExpect.withMessage("Program list infos") 291 .that(list.getProgramInfos(firstProgram.getSelector().getPrimaryId())) 292 .contains(firstProgram); 293 } finally { 294 list.close(); 295 } 296 } 297 298 @Test 299 @ApiTest(apis = {"android.hardware.radio.RadioManager.ProgramInfo#getAlert", 300 "android.hardware.radio.RadioAlert#getMessageType", 301 "android.hardware.radio.RadioAlert#getStatus", 302 "android.hardware.radio.RadioAlert#getInfoList", 303 "android.hardware.radio.RadioAlert.AlertInfo#getCategories", 304 "android.hardware.radio.RadioAlert.AlertInfo#getUrgency", 305 "android.hardware.radio.RadioAlert.AlertInfo#getSeverity", 306 "android.hardware.radio.RadioAlert.AlertInfo#getDescription", 307 "android.hardware.radio.RadioAlert.AlertInfo#getLanguage", 308 "android.hardware.radio.RadioAlert.AlertInfo#getAreas", 309 "android.hardware.radio.RadioAlert.AlertArea#getGeocodes", 310 "android.hardware.radio.RadioAlert.AlertArea#getPolygons", 311 "android.hardware.radio.RadioAlert.Geocode#getValueName", 312 "android.hardware.radio.RadioAlert.Geocode#getValue", 313 "android.hardware.radio.RadioAlert.Polygon#getCoordinates", 314 "android.hardware.radio.RadioAlert.Coordinate#getLatitude", 315 "android.hardware.radio.RadioAlert.Coordinate#getLongitude"}) 316 @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM) getAlert_forDynamicProgramListAndCurrentProgramInfo()317 public void getAlert_forDynamicProgramListAndCurrentProgramInfo() throws Exception { 318 openAmFmTuner(); 319 TestOnCompleteListener completeListener = new TestOnCompleteListener(); 320 ProgramList list = assumeNonNullProgramList(); 321 try { 322 list.addOnCompleteListener(completeListener); 323 mExpect.withMessage("List update completion before getting program info") 324 .that(completeListener.waitForCallback()).isTrue(); 325 List<RadioManager.ProgramInfo> programInfoList = list.toList(); 326 int alertCount = 0; 327 328 for (int i = 0; i < programInfoList.size(); i++) { 329 RadioAlert alert = programInfoList.get(i).getAlert(); 330 if (alert == null) { 331 continue; 332 } 333 alertCount++; 334 verifyRadioAlert(alert); 335 } 336 if (mCallback.currentProgramInfo != null 337 && mCallback.currentProgramInfo.getAlert() != null) { 338 verifyRadioAlert(mCallback.currentProgramInfo.getAlert()); 339 alertCount++; 340 } 341 assume().withMessage("Non-null alert in program list and current program info") 342 .that(alertCount).isNotEqualTo(0); 343 } finally { 344 list.close(); 345 } 346 } 347 348 @Test 349 @ApiTest(apis = {"android.hardware.radio.RadioTuner#getDynamicProgramList", 350 "android.hardware.radio.RadioTuner#tune"}) tune_withHdSelectorFromDynamicProgramList()351 public void tune_withHdSelectorFromDynamicProgramList() throws Exception { 352 ProgramList.Filter hdFilter = new ProgramList.Filter( 353 Set.of(ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT), Collections.emptySet(), 354 /* includeCategories= */ true, /* excludeModifications= */ false); 355 openAmFmTuner(); 356 TestOnCompleteListener completeListener = new TestOnCompleteListener(); 357 ProgramList list = assumeNonNullFiltereredDynamicProgramList(hdFilter); 358 try { 359 list.addOnCompleteListener(completeListener); 360 mExpect.withMessage("HD program list update completion") 361 .that(completeListener.waitForCallback()).isTrue(); 362 List<RadioManager.ProgramInfo> programInfoList = list.toList(); 363 assume().withMessage("Non-empty HD radio program program list") 364 .that(programInfoList).isNotEmpty(); 365 ProgramSelector firstHdSelector = programInfoList.get(0).getSelector(); 366 367 mRadioTuner.tune(firstHdSelector); 368 369 mExpect.withMessage("Program info callback for HD tune operation") 370 .that(mCallback.waitForProgramInfoChangeCallback(TUNE_CALLBACK_TIMEOUT_MS)) 371 .isTrue(); 372 mExpect.withMessage("HD program selector tuned to") 373 .that(mCallback.currentProgramInfo.getSelector()).isEqualTo(firstHdSelector); 374 } finally { 375 list.close(); 376 } 377 } 378 379 @Test 380 @ApiTest(apis = {"android.hardware.radio.RadioMetadata#getBitmapId", 381 "android.hardware.radio.RadioTuner#getMetadataImage"}) 382 @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED) getMetadataImage()383 public void getMetadataImage() throws Exception { 384 openAmFmTuner(); 385 TestOnCompleteListener completeListener = new TestOnCompleteListener(); 386 try (ProgramList list = assumeNonNullProgramList()) { 387 list.addOnCompleteListener(completeListener); 388 mExpect.withMessage("Program list update completion") 389 .that(completeListener.waitForCallback()).isTrue(); 390 ProgramSelector selector = getSelectorWithBitmap(list.toList()); 391 assume().withMessage("Program selector of program info with bitmap key in program list") 392 .that(selector).isNotNull(); 393 mRadioTuner.tune(selector); 394 mExpect.withMessage("Program info callback for tune operation") 395 .that(mCallback.waitForProgramInfoChangeCallback(TUNE_CALLBACK_TIMEOUT_MS)) 396 .isTrue(); 397 RadioManager.ProgramInfo currentInfo = mCallback.currentProgramInfo; 398 mCallback.reset(); 399 int bitmapId = assumeImageMetadataId(currentInfo); 400 401 Bitmap bitmap = mRadioTuner.getMetadataImage(bitmapId); 402 403 mExpect.withMessage("Bitmap metadata").that(bitmap).isNotNull(); 404 } catch (IllegalArgumentException e) { 405 mExpect.withMessage("Exception for bitmap unavailable").that(e) 406 .hasMessageThat().contains("not available"); 407 mExpect.withMessage("Bitmap metadata without updated from tuner") 408 .that(mCallback.currentProgramInfo).isNotNull(); 409 } 410 } 411 412 @Test 413 @ApiTest(apis = {"android.hardware.radio.RadioTuner#isConfigFlagSupported", 414 "android.hardware.radio.RadioTuner#isConfigFlagSet"}) isConfigFlagSet_forTunerNotSupported_throwsException()415 public void isConfigFlagSet_forTunerNotSupported_throwsException() throws Exception { 416 openAmFmTuner(); 417 boolean isSupported = mRadioTuner.isConfigFlagSupported(TEST_CONFIG_FLAG); 418 assumeFalse("Config flag supported", isSupported); 419 420 assertThrows(UnsupportedOperationException.class, 421 () -> mRadioTuner.isConfigFlagSet(TEST_CONFIG_FLAG)); 422 } 423 424 @Test 425 @ApiTest(apis = {"android.hardware.radio.RadioTuner#setConfigFlag", 426 "android.hardware.radio.RadioTuner#setConfigFlag"}) setConfigFlag_forTunerNotSupported_throwsException()427 public void setConfigFlag_forTunerNotSupported_throwsException() throws Exception { 428 openAmFmTuner(); 429 boolean isSupported = mRadioTuner.isConfigFlagSupported(TEST_CONFIG_FLAG); 430 assumeFalse("Config flag supported", isSupported); 431 432 assertThrows(UnsupportedOperationException.class, 433 () -> mRadioTuner.setConfigFlag(TEST_CONFIG_FLAG, /* value= */ true)); 434 } 435 436 @Test 437 @ApiTest(apis = {"android.hardware.radio.RadioTuner#setConfigFlag", 438 "android.hardware.radio.RadioTuner#setConfigFlag", 439 "android.hardware.radio.RadioTuner#isConfigFlagSet"}) setConfigFlag_withTrue_succeeds()440 public void setConfigFlag_withTrue_succeeds() throws Exception { 441 openAmFmTuner(); 442 boolean isSupported = mRadioTuner.isConfigFlagSupported(TEST_CONFIG_FLAG); 443 assumeTrue("Config flag supported", isSupported); 444 445 mRadioTuner.setConfigFlag(TEST_CONFIG_FLAG, /* value= */ true); 446 447 mExpect.withMessage("Config flag with true value") 448 .that(mRadioTuner.isConfigFlagSet(TEST_CONFIG_FLAG)).isTrue(); 449 mExpect.withMessage("Config flag callback count when setting true config flag value") 450 .that(mCallback.configFlagCount).isEqualTo(0); 451 } 452 453 @Test 454 @ApiTest(apis = {"android.hardware.radio.RadioTuner#setConfigFlag", 455 "android.hardware.radio.RadioTuner#setConfigFlag", 456 "android.hardware.radio.RadioTuner#isConfigFlagSet"}) setConfigFlag_withFalse_succeeds()457 public void setConfigFlag_withFalse_succeeds() throws Exception { 458 openAmFmTuner(); 459 boolean isSupported = mRadioTuner.isConfigFlagSupported(TEST_CONFIG_FLAG); 460 assumeTrue("Config flag supported", isSupported); 461 462 mRadioTuner.setConfigFlag(TEST_CONFIG_FLAG, /* value= */ false); 463 464 mExpect.withMessage("Config flag with false value") 465 .that(mRadioTuner.isConfigFlagSet(TEST_CONFIG_FLAG)).isFalse(); 466 mExpect.withMessage("Config flag callback count when setting false config flag value") 467 .that(mCallback.configFlagCount).isEqualTo(0); 468 } 469 assumeNonNullProgramList()470 private ProgramList assumeNonNullProgramList() { 471 return assumeNonNullFiltereredDynamicProgramList(/* filter= */ null); 472 } 473 assumeNonNullFiltereredDynamicProgramList(ProgramList.Filter filter)474 private ProgramList assumeNonNullFiltereredDynamicProgramList(ProgramList.Filter filter) { 475 ProgramList list = mRadioTuner.getDynamicProgramList(filter); 476 assume().withMessage("Dynamic program list supported").that(list).isNotNull(); 477 return list; 478 } 479 getSelectorWithBitmap(List<RadioManager.ProgramInfo> programInfoList)480 private ProgramSelector getSelectorWithBitmap(List<RadioManager.ProgramInfo> programInfoList) { 481 for (int i = 0; i < programInfoList.size(); i++) { 482 RadioMetadata metadata = programInfoList.get(i).getMetadata(); 483 int iconId = metadata.getBitmapId(RadioMetadata.METADATA_KEY_ICON); 484 if (iconId != 0) { 485 return programInfoList.get(i).getSelector(); 486 } 487 int artId = metadata.getBitmapId(RadioMetadata.METADATA_KEY_ART); 488 if (artId != 0) { 489 return programInfoList.get(i).getSelector(); 490 } 491 } 492 return null; 493 } 494 assumeImageMetadataId(RadioManager.ProgramInfo info)495 private int assumeImageMetadataId(RadioManager.ProgramInfo info) { 496 RadioMetadata metadata = info.getMetadata(); 497 int iconId = metadata.getBitmapId(RadioMetadata.METADATA_KEY_ICON); 498 int artId = metadata.getBitmapId(RadioMetadata.METADATA_KEY_ART); 499 assumeTrue("Bitmap id not supported", iconId != 0 || artId != 0); 500 if (iconId != 0) { 501 return iconId; 502 } else { 503 return artId; 504 } 505 } 506 verifyRadioAlert(RadioAlert alert)507 private void verifyRadioAlert(RadioAlert alert) { 508 mExpect.withMessage("Alert message type").that(alert.getMessageType()) 509 .isIn(Range.closed(RadioAlert.MESSAGE_TYPE_ALERT, 510 RadioAlert.MESSAGE_TYPE_CANCEL)); 511 mExpect.withMessage("Alert status").that(alert.getStatus()) 512 .isIn(Range.closed(RadioAlert.STATUS_ACTUAL, RadioAlert.STATUS_TEST)); 513 List<RadioAlert.AlertInfo> alertInfos = alert.getInfoList(); 514 for (int infoIdx = 0; infoIdx < alertInfos.size(); infoIdx++) { 515 RadioAlert.AlertInfo alertInfo = alertInfos.get(infoIdx); 516 int[] categories = alertInfo.getCategories(); 517 mExpect.withMessage("Non-empty category array") 518 .that(categories.length).isNotEqualTo(0); 519 for (int categoryIdx = 0; categoryIdx < categories.length; categoryIdx++) { 520 mExpect.withMessage("Alert category").that(categories[categoryIdx]) 521 .isIn(Range.closed(RadioAlert.CERTAINTY_OBSERVED, 522 RadioAlert.CATEGORY_OTHER)); 523 } 524 mExpect.withMessage("Alert urgency").that(alertInfo.getUrgency()) 525 .isIn(Range.closed(RadioAlert.URGENCY_IMMEDIATE, 526 RadioAlert.URGENCY_UNKNOWN)); 527 mExpect.withMessage("Alert severity").that(alertInfo.getSeverity()) 528 .isIn(Range.closed(RadioAlert.SEVERITY_EXTREME, 529 RadioAlert.SEVERITY_UNKNOWN)); 530 mExpect.withMessage("Alert certainty").that(alertInfo.getCertainty()) 531 .isIn(Range.closed(RadioAlert.CERTAINTY_OBSERVED, 532 RadioAlert.CERTAINTY_UNKNOWN)); 533 String alertLanguage = alertInfo.getLanguage(); 534 mExpect.withMessage("Alert description").that(alertInfo.toString()) 535 .contains(alertInfo.getDescription()); 536 if (alertLanguage != null) { 537 mExpect.withMessage("Non-empty alert language").that(alertLanguage) 538 .isNotEmpty(); 539 } 540 List<RadioAlert.AlertArea> alertAreas = alertInfo.getAreas(); 541 for (int areaIdx = 0; areaIdx < alertAreas.size(); areaIdx++) { 542 RadioAlert.AlertArea alertArea = alertAreas.get(areaIdx); 543 for (int geocodeIdx = 0; geocodeIdx < alertArea.getGeocodes().size(); 544 geocodeIdx++) { 545 mExpect.withMessage("Non-empty geocode value name").that(alertArea 546 .getGeocodes().get(geocodeIdx).getValueName()).isNotEmpty(); 547 mExpect.withMessage("Non-empty geocode value").that(alertArea 548 .getGeocodes().get(geocodeIdx).getValue()).isNotEmpty(); 549 } 550 for (int polygonIdx = 0; polygonIdx < alertArea.getPolygons().size(); 551 polygonIdx++) { 552 List<RadioAlert.Coordinate> coordinates = alertArea.getPolygons() 553 .get(polygonIdx).getCoordinates(); 554 mExpect.withMessage("Polygon coordinate counts") 555 .that(coordinates.size()).isAtLeast(4); 556 mExpect.withMessage("Latitude for the first and last coordinate") 557 .that(coordinates.getFirst().getLatitude()) 558 .isEqualTo(coordinates.getLast().getLatitude()); 559 mExpect.withMessage("Longitude for the first and last coordinate") 560 .that(coordinates.getFirst().getLongitude()) 561 .isEqualTo(coordinates.getLast().getLongitude()); 562 for (int coordinateIdx = 0; coordinateIdx < coordinates.size(); 563 coordinateIdx++) { 564 mExpect.withMessage("Latitude of polygon area") 565 .that(coordinates.get(coordinateIdx).getLatitude()) 566 .isIn(Range.closed(-90.0, 90.0)); 567 mExpect.withMessage("Longitude of polygon area") 568 .that(coordinates.get(coordinateIdx).getLongitude()) 569 .isIn(Range.closed(-180.0, 180.0)); 570 } 571 } 572 } 573 } 574 } 575 576 private static final class TestOnCompleteListener implements ProgramList.OnCompleteListener { 577 578 private final CountDownLatch mOnCompleteLatch = new CountDownLatch(1); 579 waitForCallback()580 public boolean waitForCallback() throws InterruptedException { 581 return mOnCompleteLatch.await(PROGRAM_LIST_COMPLETE_TIMEOUT_MS, TimeUnit.MILLISECONDS); 582 } 583 584 @Override onComplete()585 public void onComplete() { 586 mOnCompleteLatch.countDown(); 587 } 588 589 } 590 } 591