1 /* 2 * Copyright 2020 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.hdmicec.cts; 18 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import static org.junit.Assume.assumeTrue; 22 23 import android.hdmicec.cts.HdmiCecConstants.CecDeviceType; 24 import android.hdmicec.cts.error.DumpsysParseException; 25 26 import com.android.tradefed.config.Option; 27 import com.android.tradefed.config.OptionClass; 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 31 32 import org.junit.Before; 33 import org.junit.rules.TestRule; 34 35 import java.io.BufferedReader; 36 import java.io.IOException; 37 import java.io.StringReader; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.concurrent.Callable; 41 import java.util.concurrent.TimeUnit; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 45 /** Base class for all HDMI CEC CTS tests. */ 46 @OptionClass(alias = "hdmi-cec-client-cts-test") 47 public class BaseHdmiCecCtsTest extends BaseHostJUnit4Test { 48 49 public static final String PROPERTY_LOCALE = "persist.sys.locale"; 50 private static final String POWER_CONTROL_MODE = "power_control_mode"; 51 private static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST = 52 "power_state_change_on_active_source_lost"; 53 private static final String SET_MENU_LANGUAGE = "set_menu_language"; 54 private static final String SET_MENU_LANGUAGE_ENABLED = "1"; 55 56 private static final String SOUNDBAR_MODE = "soundbar_mode"; 57 private static final String SETTING_VOLUME_CONTROL_ENABLED = "volume_control_enabled"; 58 59 /** Enum contains the list of possible address types. */ 60 private enum AddressType { 61 DUMPSYS_AS_LOGICAL_ADDRESS("activeSourceLogicalAddress"), 62 DUMPSYS_PHYSICAL_ADDRESS("physicalAddress"); 63 64 private String address; 65 getAddressType()66 public String getAddressType() { 67 return this.address; 68 } 69 AddressType(String address)70 private AddressType(String address) { 71 this.address = address; 72 } 73 } 74 75 public final HdmiCecClientWrapper hdmiCecClient; 76 public List<LogicalAddress> mDutLogicalAddresses = new ArrayList<>(); 77 public @CecDeviceType int mTestDeviceType; 78 79 /** 80 * Constructor for BaseHdmiCecCtsTest. 81 */ BaseHdmiCecCtsTest()82 public BaseHdmiCecCtsTest() { 83 this(HdmiCecConstants.CEC_DEVICE_TYPE_UNKNOWN); 84 } 85 86 /** 87 * Constructor for BaseHdmiCecCtsTest. 88 * 89 * @param clientParams Extra parameters to use when launching cec-client 90 */ BaseHdmiCecCtsTest(String... clientParams)91 public BaseHdmiCecCtsTest(String... clientParams) { 92 this(HdmiCecConstants.CEC_DEVICE_TYPE_UNKNOWN, clientParams); 93 } 94 95 /** 96 * Constructor for BaseHdmiCecCtsTest. 97 * 98 * @param testDeviceType The primary test device type. This is used to determine to which 99 * logical address of the DUT messages should be sent. 100 * @param clientParams Extra parameters to use when launching cec-client 101 */ BaseHdmiCecCtsTest(@ecDeviceType int testDeviceType, String... clientParams)102 public BaseHdmiCecCtsTest(@CecDeviceType int testDeviceType, String... clientParams) { 103 this.hdmiCecClient = new HdmiCecClientWrapper(clientParams); 104 mTestDeviceType = testDeviceType; 105 } 106 107 @Before setUp()108 public void setUp() throws Exception { 109 setCec14(); 110 111 mDutLogicalAddresses = getDumpsysLogicalAddresses(); 112 hdmiCecClient.setTargetLogicalAddress(getTargetLogicalAddress()); 113 boolean startAsTv = !hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV); 114 hdmiCecClient.init(startAsTv, getDevice()); 115 } 116 117 /** Class with predefined rules which can be used by HDMI CEC CTS tests. */ 118 public static class CecRules { 119 requiresCec(BaseHostJUnit4Test testPointer)120 public static TestRule requiresCec(BaseHostJUnit4Test testPointer) { 121 return new RequiredFeatureRule(testPointer, HdmiCecConstants.HDMI_CEC_FEATURE); 122 } 123 requiresLeanback(BaseHostJUnit4Test testPointer)124 public static TestRule requiresLeanback(BaseHostJUnit4Test testPointer) { 125 return new RequiredFeatureRule(testPointer, HdmiCecConstants.LEANBACK_FEATURE); 126 } 127 requiresDeviceType( BaseHostJUnit4Test testPointer, @CecDeviceType int dutDeviceType)128 public static TestRule requiresDeviceType( 129 BaseHostJUnit4Test testPointer, @CecDeviceType int dutDeviceType) { 130 return RequiredDeviceTypeRule.requiredDeviceType( 131 testPointer, 132 dutDeviceType); 133 } 134 requiresArcSupport( BaseHostJUnit4Test testPointer, boolean arcSupport)135 public static TestRule requiresArcSupport( 136 BaseHostJUnit4Test testPointer, boolean arcSupport) { 137 return RequiredPropertyRule.asCsvContainsValue( 138 testPointer, 139 HdmiCecConstants.PROPERTY_ARC_SUPPORT, 140 Boolean.toString(arcSupport)); 141 } 142 143 /** This rule will skip the test if the DUT belongs to the HDMI device type deviceType. */ skipDeviceType( BaseHostJUnit4Test testPointer, @CecDeviceType int deviceType)144 public static TestRule skipDeviceType( 145 BaseHostJUnit4Test testPointer, @CecDeviceType int deviceType) { 146 return RequiredDeviceTypeRule.invalidDeviceType( 147 testPointer, 148 deviceType); 149 } 150 151 /** This rule will skip the test if the DUT is an emulator. */ requiresPhysicalDevice(BaseHostJUnit4Test testPointer)152 public static TestRule requiresPhysicalDevice(BaseHostJUnit4Test testPointer) { 153 return RequiredDeviceTypeRule.requiredPhysicalDevice(testPointer); 154 } 155 } 156 157 /** @deprecated not used anymore **/ 158 @Deprecated 159 @Option(name = HdmiCecConstants.PHYSICAL_ADDRESS_NAME, 160 description = "HDMI CEC physical address of the DUT", 161 mandatory = false) 162 public static int dutPhysicalAddress = HdmiCecConstants.DEFAULT_PHYSICAL_ADDRESS; 163 164 /** Gets the physical address of the DUT by parsing the dumpsys hdmi_control. */ getDumpsysPhysicalAddress()165 public int getDumpsysPhysicalAddress() throws DumpsysParseException { 166 return getDumpsysPhysicalAddress(getDevice()); 167 } 168 169 /** Gets the physical address of the specified device by parsing the dumpsys hdmi_control. */ getDumpsysPhysicalAddress(ITestDevice device)170 public static int getDumpsysPhysicalAddress(ITestDevice device) throws DumpsysParseException { 171 return parseRequiredAddressFromDumpsys(device, AddressType.DUMPSYS_PHYSICAL_ADDRESS); 172 } 173 174 /** Gets the list of logical addresses of the DUT by parsing the dumpsys hdmi_control. */ getDumpsysLogicalAddresses()175 public List<LogicalAddress> getDumpsysLogicalAddresses() throws DumpsysParseException { 176 return getDumpsysLogicalAddresses(getDevice()); 177 } 178 179 /** Gets the list of logical addresses of the device by parsing the dumpsys hdmi_control. */ getDumpsysLogicalAddresses(ITestDevice device)180 public static List<LogicalAddress> getDumpsysLogicalAddresses(ITestDevice device) 181 throws DumpsysParseException { 182 List<LogicalAddress> logicalAddressList = new ArrayList<>(); 183 String line; 184 String pattern = 185 "(.*?)" 186 + "(mDeviceInfo:)(.*)(logical_address: )" 187 + "(?<" 188 + "logicalAddress" 189 + ">0x\\p{XDigit}{2})" 190 + "(.*?)"; 191 Pattern p = Pattern.compile(pattern); 192 try { 193 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 194 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 195 while ((line = reader.readLine()) != null) { 196 Matcher m = p.matcher(line); 197 if (m.matches()) { 198 int address = Integer.decode(m.group("logicalAddress")); 199 LogicalAddress logicalAddress = LogicalAddress.getLogicalAddress(address); 200 logicalAddressList.add(logicalAddress); 201 } 202 } 203 if (!logicalAddressList.isEmpty()) { 204 return logicalAddressList; 205 } 206 } catch (IOException | DeviceNotAvailableException e) { 207 throw new DumpsysParseException( 208 "Could not parse logicalAddress from dumpsys.", e); 209 } 210 throw new DumpsysParseException( 211 "Could not parse logicalAddress from dumpsys."); 212 } 213 214 /** 215 * Gets the system audio mode status of the device by parsing the dumpsys hdmi_control. Returns 216 * true when system audio mode is on and false when system audio mode is off 217 */ isSystemAudioModeOn(ITestDevice device)218 public boolean isSystemAudioModeOn(ITestDevice device) throws DumpsysParseException { 219 List<LogicalAddress> logicalAddressList = new ArrayList<>(); 220 String line; 221 String pattern = 222 "(.*?)" 223 + "(mSystemAudioActivated: )" 224 + "(?<" 225 + "systemAudioModeStatus" 226 + ">[true|false])" 227 + "(.*?)"; 228 Pattern p = Pattern.compile(pattern); 229 try { 230 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 231 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 232 while ((line = reader.readLine()) != null) { 233 Matcher m = p.matcher(line); 234 if (m.matches()) { 235 return m.group("systemAudioModeStatus").equals("true"); 236 } 237 } 238 } catch (IOException | DeviceNotAvailableException e) { 239 throw new DumpsysParseException("Could not parse system audio mode from dumpsys.", e); 240 } 241 throw new DumpsysParseException("Could not parse system audio mode from dumpsys."); 242 } 243 244 /** Gets the DUT's logical address to which messages should be sent */ getTargetLogicalAddress()245 public LogicalAddress getTargetLogicalAddress() throws DumpsysParseException { 246 return getTargetLogicalAddress(getDevice(), mTestDeviceType); 247 } 248 249 /** Gets the given device's logical address to which messages should be sent */ getTargetLogicalAddress(ITestDevice device)250 public static LogicalAddress getTargetLogicalAddress(ITestDevice device) 251 throws DumpsysParseException { 252 return getTargetLogicalAddress(device, HdmiCecConstants.CEC_DEVICE_TYPE_UNKNOWN); 253 } 254 255 /** 256 * Gets the given device's logical address to which messages should be sent, based on the test 257 * device type. 258 * 259 * When the test doesn't specify a device type, or the device doesn't have a logical address 260 * that matches the specified device type, use the first logical address. 261 */ getTargetLogicalAddress(ITestDevice device, int testDeviceType)262 public static LogicalAddress getTargetLogicalAddress(ITestDevice device, int testDeviceType) 263 throws DumpsysParseException { 264 List<LogicalAddress> logicalAddressList = getDumpsysLogicalAddresses(device); 265 for (LogicalAddress address : logicalAddressList) { 266 if (address.getDeviceType() == testDeviceType) { 267 return address; 268 } 269 } 270 return logicalAddressList.get(0); 271 } 272 273 /** 274 * Parses the dumpsys hdmi_control to get the logical address of the current device registered 275 * as active source. 276 */ getDumpsysActiveSourceLogicalAddress()277 public LogicalAddress getDumpsysActiveSourceLogicalAddress() throws DumpsysParseException { 278 ITestDevice device = getDevice(); 279 int address = 280 parseRequiredAddressFromDumpsys(device, AddressType.DUMPSYS_AS_LOGICAL_ADDRESS); 281 return LogicalAddress.getLogicalAddress(address); 282 } 283 parseRequiredAddressFromDumpsys(ITestDevice device, AddressType addressType)284 private static int parseRequiredAddressFromDumpsys(ITestDevice device, AddressType addressType) 285 throws DumpsysParseException { 286 Matcher m; 287 String line; 288 String pattern; 289 switch (addressType) { 290 case DUMPSYS_PHYSICAL_ADDRESS: 291 pattern = 292 "(.*?)" 293 + "(physical_address: )" 294 + "(?<" 295 + addressType.getAddressType() 296 + ">0x\\p{XDigit}{4})" 297 + "(.*?)"; 298 break; 299 case DUMPSYS_AS_LOGICAL_ADDRESS: 300 pattern = 301 "(.*?)" 302 + "(mActiveSource: )" 303 + "(\\(0x)" 304 + "(?<" 305 + addressType.getAddressType() 306 + ">\\d+)" 307 + "(, )" 308 + "(0x)" 309 + "(?<physicalAddress>\\d+)" 310 + "(\\))" 311 + "(.*?)"; 312 break; 313 default: 314 throw new DumpsysParseException( 315 "Incorrect parameters", new IllegalArgumentException()); 316 } 317 318 try { 319 Pattern p = Pattern.compile(pattern); 320 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 321 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 322 while ((line = reader.readLine()) != null) { 323 m = p.matcher(line); 324 if (m.matches()) { 325 int address = Integer.decode(m.group(addressType.getAddressType())); 326 return address; 327 } 328 } 329 } catch (IOException | DeviceNotAvailableException e) { 330 throw new DumpsysParseException( 331 "Could not parse " + addressType.getAddressType() + " from dumpsys.", e); 332 } 333 throw new DumpsysParseException( 334 "Could not parse " + addressType.getAddressType() + " from dumpsys."); 335 } 336 hasDeviceType(@ecDeviceType int deviceType)337 public boolean hasDeviceType(@CecDeviceType int deviceType) { 338 for (LogicalAddress address : mDutLogicalAddresses) { 339 if (address.getDeviceType() == deviceType) { 340 return true; 341 } 342 } 343 return false; 344 } 345 hasLogicalAddress(LogicalAddress address)346 public boolean hasLogicalAddress(LogicalAddress address) { 347 return mDutLogicalAddresses.contains(address); 348 } 349 setCecVersion(ITestDevice device, int cecVersion)350 private static void setCecVersion(ITestDevice device, int cecVersion) throws Exception { 351 device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_version " + 352 cecVersion); 353 354 TimeUnit.SECONDS.sleep(HdmiCecConstants.TIMEOUT_CEC_REINIT_SECONDS); 355 } 356 357 /** 358 * Configures the device to use CEC 2.0. Skips the test if the device does not support CEC 2.0. 359 * @throws Exception 360 */ setCec20()361 public void setCec20() throws Exception { 362 setCecVersion(getDevice(), HdmiCecConstants.CEC_VERSION_2_0); 363 hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GET_CEC_VERSION); 364 String reportCecVersion = hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(), 365 CecOperand.CEC_VERSION); 366 boolean supportsCec2 = CecMessage.getParams(reportCecVersion) 367 >= HdmiCecConstants.CEC_VERSION_2_0; 368 369 // Device still reports a CEC version < 2.0. 370 assumeTrue(supportsCec2); 371 } 372 setCec14()373 public void setCec14() throws Exception { 374 setCecVersion(getDevice(), HdmiCecConstants.CEC_VERSION_1_4); 375 } 376 getSystemLocale()377 public String getSystemLocale() throws Exception { 378 ITestDevice device = getDevice(); 379 return device.executeShellCommand("getprop " + PROPERTY_LOCALE).trim(); 380 } 381 extractLanguage(String locale)382 public static String extractLanguage(String locale) { 383 return locale.split("[^a-zA-Z]")[0]; 384 } 385 setSystemLocale(String locale)386 public void setSystemLocale(String locale) throws Exception { 387 ITestDevice device = getDevice(); 388 device.executeShellCommand("setprop " + PROPERTY_LOCALE + " " + locale); 389 } 390 isLanguageEditable()391 public boolean isLanguageEditable() throws Exception { 392 return getSettingsValue(SET_MENU_LANGUAGE).equals(SET_MENU_LANGUAGE_ENABLED); 393 } 394 getSettingsValue(ITestDevice device, String setting)395 public static String getSettingsValue(ITestDevice device, String setting) throws Exception { 396 return device.executeShellCommand("cmd hdmi_control cec_setting get " + setting) 397 .replace(setting + " = ", "").trim(); 398 } 399 getSettingsValue(String setting)400 public String getSettingsValue(String setting) throws Exception { 401 return getSettingsValue(getDevice(), setting); 402 } 403 setSettingsValue(ITestDevice device, String setting, String value)404 public static String setSettingsValue(ITestDevice device, String setting, String value) 405 throws Exception { 406 String val = getSettingsValue(device, setting); 407 device.executeShellCommand("cmd hdmi_control cec_setting set " + setting + " " + 408 value); 409 return val; 410 } 411 setSettingsValue(String setting, String value)412 public String setSettingsValue(String setting, String value) throws Exception { 413 return setSettingsValue(getDevice(), setting, value); 414 } 415 getDeviceList()416 public String getDeviceList() throws Exception { 417 return getDevice().executeShellCommand( 418 "dumpsys hdmi_control | sed -n '/mDeviceInfos/,/mCecController/{//!p;}'"); 419 } 420 getLocalDevicesList()421 public String getLocalDevicesList() throws Exception { 422 return getDevice().executeShellCommand( 423 "dumpsys hdmi_control | sed -n '/HdmiCecLocalDevice /p'\n"); 424 } 425 sendDeviceToSleepAndValidate()426 public void sendDeviceToSleepAndValidate() throws Exception { 427 sendDeviceToSleep(); 428 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP); 429 } 430 waitForTransitionTo(int finalState)431 public void waitForTransitionTo(int finalState) throws Exception { 432 int powerStatus; 433 int waitTimeSeconds = 0; 434 LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice(); 435 int transitionState; 436 if (finalState == HdmiCecConstants.CEC_POWER_STATUS_STANDBY) { 437 transitionState = HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_STANDBY; 438 } else if (finalState == HdmiCecConstants.CEC_POWER_STATUS_ON) { 439 transitionState = HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_ON; 440 } else { 441 throw new Exception("Unsupported final power state!"); 442 } 443 // We first give 2 seconds to enter the transition state, and then 444 // MAX_SLEEP_TIME_SECONDS to go from the transition state to the final state. 445 do { 446 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 447 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 448 hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS); 449 powerStatus = 450 CecMessage.getParams( 451 hdmiCecClient.checkExpectedOutput( 452 cecClientDevice, CecOperand.REPORT_POWER_STATUS)); 453 if (powerStatus == finalState) { 454 return; 455 } 456 } while (waitTimeSeconds <= HdmiCecConstants.SLEEP_TIME_DELAY_SECONDS); 457 do { 458 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 459 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 460 hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS); 461 powerStatus = 462 CecMessage.getParams( 463 hdmiCecClient.checkExpectedOutput( 464 cecClientDevice, CecOperand.REPORT_POWER_STATUS)); 465 if (powerStatus == finalState) { 466 return; 467 } 468 } while (powerStatus == transitionState 469 && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS); 470 if (powerStatus != finalState) { 471 // Transition not complete even after wait, throw an Exception. 472 throw new Exception("Power status did not change to expected state."); 473 } 474 } 475 sendDeviceToSleepWithoutWait()476 public void sendDeviceToSleepWithoutWait() throws Exception { 477 ITestDevice device = getDevice(); 478 WakeLockHelper.acquirePartialWakeLock(device); 479 device.executeShellCommand("input keyevent KEYCODE_SLEEP"); 480 } 481 sendDeviceToSleep()482 public void sendDeviceToSleep() throws Exception { 483 sendDeviceToSleepWithoutWait(); 484 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP); 485 waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY); 486 } 487 sendDeviceToSleepAndValidateUsingStandbyMessage(boolean directlyAddressed)488 public void sendDeviceToSleepAndValidateUsingStandbyMessage(boolean directlyAddressed) 489 throws Exception { 490 ITestDevice device = getDevice(); 491 WakeLockHelper.acquirePartialWakeLock(device); 492 LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice(); 493 if (directlyAddressed) { 494 hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.STANDBY); 495 } else { 496 hdmiCecClient.sendCecMessage( 497 cecClientDevice, LogicalAddress.BROADCAST, CecOperand.STANDBY); 498 } 499 waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY); 500 } 501 wakeUpDevice()502 public void wakeUpDevice() throws Exception { 503 ITestDevice device = getDevice(); 504 device.executeShellCommand("input keyevent KEYCODE_WAKEUP"); 505 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE); 506 waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON); 507 WakeLockHelper.releasePartialWakeLock(device); 508 } 509 wakeUpDeviceWithoutWait()510 public void wakeUpDeviceWithoutWait() throws Exception { 511 ITestDevice device = getDevice(); 512 device.executeShellCommand("input keyevent KEYCODE_WAKEUP"); 513 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE); 514 WakeLockHelper.releasePartialWakeLock(device); 515 } 516 checkStandbyAndWakeUp()517 public void checkStandbyAndWakeUp() throws Exception { 518 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP); 519 wakeUpDevice(); 520 } 521 assertDeviceWakefulness(String wakefulness)522 public void assertDeviceWakefulness(String wakefulness) throws Exception { 523 ITestDevice device = getDevice(); 524 String actualWakefulness; 525 int waitTimeSeconds = 0; 526 527 do { 528 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 529 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 530 actualWakefulness = 531 device.executeShellCommand("dumpsys power | grep mWakefulness=") 532 .trim().replace("mWakefulness=", ""); 533 } while (!actualWakefulness.equals(wakefulness) 534 && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS); 535 assertWithMessage( 536 "Device wakefulness is " 537 + actualWakefulness 538 + " but expected to be " 539 + wakefulness) 540 .that(actualWakefulness) 541 .isEqualTo(wakefulness); 542 } 543 544 /** 545 * Checks a given condition once every {@link HdmiCecConstants.SLEEP_TIMESTEP_SECONDS} seconds 546 * until it is true, or {@link HdmiCecConstants.MAX_SLEEP_TIME_SECONDS} seconds have passed. 547 * Triggers an assertion failure if the condition remains false after the time limit. 548 * @param condition Callable that returns whether the condition is met 549 * @param errorMessage The message to print if the condition is false 550 */ waitForCondition(Callable<Boolean> condition, String errorMessage)551 public void waitForCondition(Callable<Boolean> condition, String errorMessage) 552 throws Exception { 553 int waitTimeSeconds = 0; 554 boolean conditionState; 555 do { 556 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 557 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 558 conditionState = condition.call(); 559 } while (!conditionState && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS); 560 assertWithMessage(errorMessage).that(conditionState).isTrue(); 561 } 562 sendOtp()563 public void sendOtp() throws Exception { 564 ITestDevice device = getDevice(); 565 device.executeShellCommand("cmd hdmi_control onetouchplay"); 566 } 567 setPowerControlMode(String valToSet)568 public String setPowerControlMode(String valToSet) throws Exception { 569 return setSettingsValue(POWER_CONTROL_MODE, valToSet); 570 } 571 setVolumeControlMode(String valToSet)572 public String setVolumeControlMode(String valToSet) throws Exception { 573 return setSettingsValue(SETTING_VOLUME_CONTROL_ENABLED, valToSet); 574 } 575 setPowerStateChangeOnActiveSourceLost(String valToSet)576 public String setPowerStateChangeOnActiveSourceLost(String valToSet) throws Exception { 577 return setSettingsValue(POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, valToSet); 578 } 579 setSoundbarMode(String valToSet)580 public String setSoundbarMode(String valToSet) throws Exception { 581 return setSettingsValue(SOUNDBAR_MODE, valToSet); 582 } 583 getSoundbarMode()584 public String getSoundbarMode() throws Exception { 585 return getSettingsValue(SOUNDBAR_MODE); 586 } 587 isDeviceActiveSource(ITestDevice device)588 public boolean isDeviceActiveSource(ITestDevice device) throws DumpsysParseException { 589 final String activeSource = "activeSource"; 590 final String pattern = 591 "(.*?)" 592 + "(isActiveSource\\(\\): )" 593 + "(?<" 594 + activeSource 595 + ">\\btrue\\b|\\bfalse\\b)" 596 + "(.*?)"; 597 try { 598 Pattern p = Pattern.compile(pattern); 599 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 600 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 601 String line; 602 while ((line = reader.readLine()) != null) { 603 Matcher matcher = p.matcher(line); 604 if (matcher.matches()) { 605 return matcher.group(activeSource).equals("true"); 606 } 607 } 608 } catch (IOException | DeviceNotAvailableException e) { 609 throw new DumpsysParseException("Could not fetch 'dumpsys hdmi_control' output.", e); 610 } 611 throw new DumpsysParseException("Could not parse isActiveSource() from dumpsys."); 612 } 613 614 /** 615 * For source devices, simulate that a sink is connected by responding to the 616 * {@code Give Power Status} message that is sent when re-enabling CEC. 617 * Validate that HdmiControlService#mIsCecAvailable is set to true as a result. 618 */ simulateCecSinkConnected(ITestDevice device, LogicalAddress source)619 public void simulateCecSinkConnected(ITestDevice device, LogicalAddress source) 620 throws Exception { 621 hdmiCecClient.clearClientOutput(); 622 device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0"); 623 waitForCondition(() -> !isCecAvailable(device), "Could not disable CEC"); 624 device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1"); 625 // When a CEC device has just become available, the CEC adapter isn't able to send it 626 // messages right away. Therefore we let the first <Give Power Status> message time-out, and 627 // only respond to the retry. 628 hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS); 629 hdmiCecClient.clearClientOutput(); 630 hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS); 631 hdmiCecClient.sendCecMessage(LogicalAddress.TV, source, CecOperand.REPORT_POWER_STATUS, 632 CecMessage.formatParams(HdmiCecConstants.CEC_POWER_STATUS_STANDBY)); 633 waitForCondition(() -> isCecAvailable(device), 634 "Simulating that a sink is connected, failed."); 635 } 636 isCecAvailable(ITestDevice device)637 boolean isCecAvailable(ITestDevice device) throws Exception { 638 return device.executeShellCommand("dumpsys hdmi_control | grep mIsCecAvailable:") 639 .replace("mIsCecAvailable:", "").trim().equals("true"); 640 } 641 isCecEnabled(ITestDevice device)642 boolean isCecEnabled(ITestDevice device) throws Exception { 643 return device.executeShellCommand("dumpsys hdmi_control | grep hdmi_cec_enabled") 644 .trim().startsWith("hdmi_cec_enabled (int): 1"); 645 } 646 647 /** 648 * Checks whether an audio output device is in the list output by an ADB shell command. 649 * Intended for checking a device's volume behavior by looking at whether it appears in 650 * a certain line of the audio dumpsys. 651 */ isAudioOutputDeviceInList(int audioOutputDevice, String deviceListAdbShellCommand)652 private boolean isAudioOutputDeviceInList(int audioOutputDevice, 653 String deviceListAdbShellCommand) throws Exception { 654 String[] splitLine = getDevice().executeShellCommand(deviceListAdbShellCommand).split("="); 655 if (splitLine.length < 2) { 656 // No devices are in the list 657 return false; 658 } 659 String[] deviceStrings = splitLine[1].trim().split(","); 660 for (String deviceString : deviceStrings) { 661 try { 662 if (Integer.decode(deviceString) == audioOutputDevice) { 663 return true; 664 } 665 } catch (NumberFormatException e) { 666 // Ignore this device and continue 667 } 668 } 669 return false; 670 } 671 672 /** 673 * Returns whether a given audio output device is currently using full volume behavior 674 */ isFullVolumeDevice(int audioOutputDevice)675 public boolean isFullVolumeDevice(int audioOutputDevice) throws Exception { 676 return isAudioOutputDeviceInList(audioOutputDevice, 677 "dumpsys audio | grep mFullVolumeDevices"); 678 } 679 680 /** 681 * Returns whether a given audio output device is currently using absolute volume behavior 682 */ isAbsoluteVolumeDevice(int audioOutputDevice)683 public boolean isAbsoluteVolumeDevice(int audioOutputDevice) throws Exception { 684 // Need to explicitly exclude the line for adjust-only absolute volume devices 685 return isAudioOutputDeviceInList(audioOutputDevice, 686 "dumpsys audio | grep 'absolute volume devices' | grep -v 'adjust-only'"); 687 } 688 689 /** 690 * Returns whether a given audio output device is currently using adjust-only absolute volume 691 * behavior 692 */ isAdjustOnlyAbsoluteVolumeDevice(int audioOutputDevice)693 public boolean isAdjustOnlyAbsoluteVolumeDevice(int audioOutputDevice) throws Exception { 694 return isAudioOutputDeviceInList(audioOutputDevice, 695 "dumpsys audio | grep 'adjust-only absolute volume devices'"); 696 } 697 698 /** 699 * On Google TV devices the only stream played is STREAM_MUSIC. 700 * The method returns whether "Devices" in "STREAM_MUSIC" contains "hdmi" in audio dumpsys. 701 * This is required by tests where the DUT has to redirect volume key events as CEC 702 * <User Control Pressed> messages. 703 * This method might return false, because the set-up contains an HDMI Stub. 704 * See {@link android.media.AudioSystem#STREAM_MUSIC} and 705 * {@link android.media.AudioSystem#DEVICE_OUT_HDMI}. 706 */ isPlayingStreamMusicOnHdmiOut()707 public boolean isPlayingStreamMusicOnHdmiOut() throws DeviceNotAvailableException { 708 return getDevice().executeShellCommand("dumpsys audio | sed -n '/^- STREAM_MUSIC:/,/^$/p'" 709 + " | grep \"Devices\"").contains("hdmi"); 710 } 711 } 712