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 152 /** @deprecated not used anymore **/ 153 @Deprecated 154 @Option(name = HdmiCecConstants.PHYSICAL_ADDRESS_NAME, 155 description = "HDMI CEC physical address of the DUT", 156 mandatory = false) 157 public static int dutPhysicalAddress = HdmiCecConstants.DEFAULT_PHYSICAL_ADDRESS; 158 159 /** Gets the physical address of the DUT by parsing the dumpsys hdmi_control. */ getDumpsysPhysicalAddress()160 public int getDumpsysPhysicalAddress() throws DumpsysParseException { 161 return getDumpsysPhysicalAddress(getDevice()); 162 } 163 164 /** Gets the physical address of the specified device by parsing the dumpsys hdmi_control. */ getDumpsysPhysicalAddress(ITestDevice device)165 public static int getDumpsysPhysicalAddress(ITestDevice device) throws DumpsysParseException { 166 return parseRequiredAddressFromDumpsys(device, AddressType.DUMPSYS_PHYSICAL_ADDRESS); 167 } 168 169 /** Gets the list of logical addresses of the DUT by parsing the dumpsys hdmi_control. */ getDumpsysLogicalAddresses()170 public List<LogicalAddress> getDumpsysLogicalAddresses() throws DumpsysParseException { 171 return getDumpsysLogicalAddresses(getDevice()); 172 } 173 174 /** Gets the list of logical addresses of the device by parsing the dumpsys hdmi_control. */ getDumpsysLogicalAddresses(ITestDevice device)175 public static List<LogicalAddress> getDumpsysLogicalAddresses(ITestDevice device) 176 throws DumpsysParseException { 177 List<LogicalAddress> logicalAddressList = new ArrayList<>(); 178 String line; 179 String pattern = 180 "(.*?)" 181 + "(mDeviceInfo:)(.*)(logical_address: )" 182 + "(?<" 183 + "logicalAddress" 184 + ">0x\\p{XDigit}{2})" 185 + "(.*?)"; 186 Pattern p = Pattern.compile(pattern); 187 try { 188 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 189 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 190 while ((line = reader.readLine()) != null) { 191 Matcher m = p.matcher(line); 192 if (m.matches()) { 193 int address = Integer.decode(m.group("logicalAddress")); 194 LogicalAddress logicalAddress = LogicalAddress.getLogicalAddress(address); 195 logicalAddressList.add(logicalAddress); 196 } 197 } 198 if (!logicalAddressList.isEmpty()) { 199 return logicalAddressList; 200 } 201 } catch (IOException | DeviceNotAvailableException e) { 202 throw new DumpsysParseException( 203 "Could not parse logicalAddress from dumpsys.", e); 204 } 205 throw new DumpsysParseException( 206 "Could not parse logicalAddress from dumpsys."); 207 } 208 209 /** 210 * Gets the system audio mode status of the device by parsing the dumpsys hdmi_control. Returns 211 * true when system audio mode is on and false when system audio mode is off 212 */ isSystemAudioModeOn(ITestDevice device)213 public boolean isSystemAudioModeOn(ITestDevice device) throws DumpsysParseException { 214 List<LogicalAddress> logicalAddressList = new ArrayList<>(); 215 String line; 216 String pattern = 217 "(.*?)" 218 + "(mSystemAudioActivated: )" 219 + "(?<" 220 + "systemAudioModeStatus" 221 + ">[true|false])" 222 + "(.*?)"; 223 Pattern p = Pattern.compile(pattern); 224 try { 225 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 226 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 227 while ((line = reader.readLine()) != null) { 228 Matcher m = p.matcher(line); 229 if (m.matches()) { 230 return m.group("systemAudioModeStatus").equals("true"); 231 } 232 } 233 } catch (IOException | DeviceNotAvailableException e) { 234 throw new DumpsysParseException("Could not parse system audio mode from dumpsys.", e); 235 } 236 throw new DumpsysParseException("Could not parse system audio mode from dumpsys."); 237 } 238 239 /** Gets the DUT's logical address to which messages should be sent */ getTargetLogicalAddress()240 public LogicalAddress getTargetLogicalAddress() throws DumpsysParseException { 241 return getTargetLogicalAddress(getDevice(), mTestDeviceType); 242 } 243 244 /** Gets the given device's logical address to which messages should be sent */ getTargetLogicalAddress(ITestDevice device)245 public static LogicalAddress getTargetLogicalAddress(ITestDevice device) 246 throws DumpsysParseException { 247 return getTargetLogicalAddress(device, HdmiCecConstants.CEC_DEVICE_TYPE_UNKNOWN); 248 } 249 250 /** 251 * Gets the given device's logical address to which messages should be sent, based on the test 252 * device type. 253 * 254 * When the test doesn't specify a device type, or the device doesn't have a logical address 255 * that matches the specified device type, use the first logical address. 256 */ getTargetLogicalAddress(ITestDevice device, int testDeviceType)257 public static LogicalAddress getTargetLogicalAddress(ITestDevice device, int testDeviceType) 258 throws DumpsysParseException { 259 List<LogicalAddress> logicalAddressList = getDumpsysLogicalAddresses(device); 260 for (LogicalAddress address : logicalAddressList) { 261 if (address.getDeviceType() == testDeviceType) { 262 return address; 263 } 264 } 265 return logicalAddressList.get(0); 266 } 267 268 /** 269 * Parses the dumpsys hdmi_control to get the logical address of the current device registered 270 * as active source. 271 */ getDumpsysActiveSourceLogicalAddress()272 public LogicalAddress getDumpsysActiveSourceLogicalAddress() throws DumpsysParseException { 273 ITestDevice device = getDevice(); 274 int address = 275 parseRequiredAddressFromDumpsys(device, AddressType.DUMPSYS_AS_LOGICAL_ADDRESS); 276 return LogicalAddress.getLogicalAddress(address); 277 } 278 parseRequiredAddressFromDumpsys(ITestDevice device, AddressType addressType)279 private static int parseRequiredAddressFromDumpsys(ITestDevice device, AddressType addressType) 280 throws DumpsysParseException { 281 Matcher m; 282 String line; 283 String pattern; 284 switch (addressType) { 285 case DUMPSYS_PHYSICAL_ADDRESS: 286 pattern = 287 "(.*?)" 288 + "(physical_address: )" 289 + "(?<" 290 + addressType.getAddressType() 291 + ">0x\\p{XDigit}{4})" 292 + "(.*?)"; 293 break; 294 case DUMPSYS_AS_LOGICAL_ADDRESS: 295 pattern = 296 "(.*?)" 297 + "(mActiveSource: )" 298 + "(\\(0x)" 299 + "(?<" 300 + addressType.getAddressType() 301 + ">\\d+)" 302 + "(, )" 303 + "(0x)" 304 + "(?<physicalAddress>\\d+)" 305 + "(\\))" 306 + "(.*?)"; 307 break; 308 default: 309 throw new DumpsysParseException( 310 "Incorrect parameters", new IllegalArgumentException()); 311 } 312 313 try { 314 Pattern p = Pattern.compile(pattern); 315 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 316 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 317 while ((line = reader.readLine()) != null) { 318 m = p.matcher(line); 319 if (m.matches()) { 320 int address = Integer.decode(m.group(addressType.getAddressType())); 321 return address; 322 } 323 } 324 } catch (IOException | DeviceNotAvailableException e) { 325 throw new DumpsysParseException( 326 "Could not parse " + addressType.getAddressType() + " from dumpsys.", e); 327 } 328 throw new DumpsysParseException( 329 "Could not parse " + addressType.getAddressType() + " from dumpsys."); 330 } 331 hasDeviceType(@ecDeviceType int deviceType)332 public boolean hasDeviceType(@CecDeviceType int deviceType) { 333 for (LogicalAddress address : mDutLogicalAddresses) { 334 if (address.getDeviceType() == deviceType) { 335 return true; 336 } 337 } 338 return false; 339 } 340 hasLogicalAddress(LogicalAddress address)341 public boolean hasLogicalAddress(LogicalAddress address) { 342 return mDutLogicalAddresses.contains(address); 343 } 344 setCecVersion(ITestDevice device, int cecVersion)345 private static void setCecVersion(ITestDevice device, int cecVersion) throws Exception { 346 device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_version " + 347 cecVersion); 348 349 TimeUnit.SECONDS.sleep(HdmiCecConstants.TIMEOUT_CEC_REINIT_SECONDS); 350 } 351 352 /** 353 * Configures the device to use CEC 2.0. Skips the test if the device does not support CEC 2.0. 354 * @throws Exception 355 */ setCec20()356 public void setCec20() throws Exception { 357 setCecVersion(getDevice(), HdmiCecConstants.CEC_VERSION_2_0); 358 hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GET_CEC_VERSION); 359 String reportCecVersion = hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(), 360 CecOperand.CEC_VERSION); 361 boolean supportsCec2 = CecMessage.getParams(reportCecVersion) 362 >= HdmiCecConstants.CEC_VERSION_2_0; 363 364 // Device still reports a CEC version < 2.0. 365 assumeTrue(supportsCec2); 366 } 367 setCec14()368 public void setCec14() throws Exception { 369 setCecVersion(getDevice(), HdmiCecConstants.CEC_VERSION_1_4); 370 } 371 getSystemLocale()372 public String getSystemLocale() throws Exception { 373 ITestDevice device = getDevice(); 374 return device.executeShellCommand("getprop " + PROPERTY_LOCALE).trim(); 375 } 376 extractLanguage(String locale)377 public static String extractLanguage(String locale) { 378 return locale.split("[^a-zA-Z]")[0]; 379 } 380 setSystemLocale(String locale)381 public void setSystemLocale(String locale) throws Exception { 382 ITestDevice device = getDevice(); 383 device.executeShellCommand("setprop " + PROPERTY_LOCALE + " " + locale); 384 } 385 isLanguageEditable()386 public boolean isLanguageEditable() throws Exception { 387 return getSettingsValue(SET_MENU_LANGUAGE).equals(SET_MENU_LANGUAGE_ENABLED); 388 } 389 getSettingsValue(ITestDevice device, String setting)390 public static String getSettingsValue(ITestDevice device, String setting) throws Exception { 391 return device.executeShellCommand("cmd hdmi_control cec_setting get " + setting) 392 .replace(setting + " = ", "").trim(); 393 } 394 getSettingsValue(String setting)395 public String getSettingsValue(String setting) throws Exception { 396 return getSettingsValue(getDevice(), setting); 397 } 398 setSettingsValue(ITestDevice device, String setting, String value)399 public static String setSettingsValue(ITestDevice device, String setting, String value) 400 throws Exception { 401 String val = getSettingsValue(device, setting); 402 device.executeShellCommand("cmd hdmi_control cec_setting set " + setting + " " + 403 value); 404 return val; 405 } 406 setSettingsValue(String setting, String value)407 public String setSettingsValue(String setting, String value) throws Exception { 408 return setSettingsValue(getDevice(), setting, value); 409 } 410 getDeviceList()411 public String getDeviceList() throws Exception { 412 return getDevice().executeShellCommand( 413 "dumpsys hdmi_control | sed -n '/mDeviceInfos/,/mCecController/{//!p;}'"); 414 } 415 getLocalDevicesList()416 public String getLocalDevicesList() throws Exception { 417 return getDevice().executeShellCommand( 418 "dumpsys hdmi_control | sed -n '/HdmiCecLocalDevice /p'\n"); 419 } 420 sendDeviceToSleepAndValidate()421 public void sendDeviceToSleepAndValidate() throws Exception { 422 sendDeviceToSleep(); 423 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP); 424 } 425 waitForTransitionTo(int finalState)426 public void waitForTransitionTo(int finalState) throws Exception { 427 int powerStatus; 428 int waitTimeSeconds = 0; 429 LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice(); 430 int transitionState; 431 if (finalState == HdmiCecConstants.CEC_POWER_STATUS_STANDBY) { 432 transitionState = HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_STANDBY; 433 } else if (finalState == HdmiCecConstants.CEC_POWER_STATUS_ON) { 434 transitionState = HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_ON; 435 } else { 436 throw new Exception("Unsupported final power state!"); 437 } 438 // We first give 2 seconds to enter the transition state, and then 439 // MAX_SLEEP_TIME_SECONDS to go from the transition state to the final state. 440 do { 441 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 442 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 443 hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS); 444 powerStatus = 445 CecMessage.getParams( 446 hdmiCecClient.checkExpectedOutput( 447 cecClientDevice, CecOperand.REPORT_POWER_STATUS)); 448 if (powerStatus == finalState) { 449 return; 450 } 451 } while (waitTimeSeconds <= HdmiCecConstants.SLEEP_TIME_DELAY_SECONDS); 452 do { 453 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 454 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 455 hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS); 456 powerStatus = 457 CecMessage.getParams( 458 hdmiCecClient.checkExpectedOutput( 459 cecClientDevice, CecOperand.REPORT_POWER_STATUS)); 460 if (powerStatus == finalState) { 461 return; 462 } 463 } while (powerStatus == transitionState 464 && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS); 465 if (powerStatus != finalState) { 466 // Transition not complete even after wait, throw an Exception. 467 throw new Exception("Power status did not change to expected state."); 468 } 469 } 470 sendDeviceToSleepWithoutWait()471 public void sendDeviceToSleepWithoutWait() throws Exception { 472 ITestDevice device = getDevice(); 473 WakeLockHelper.acquirePartialWakeLock(device); 474 device.executeShellCommand("input keyevent KEYCODE_SLEEP"); 475 } 476 sendDeviceToSleep()477 public void sendDeviceToSleep() throws Exception { 478 sendDeviceToSleepWithoutWait(); 479 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP); 480 waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY); 481 } 482 sendDeviceToSleepAndValidateUsingStandbyMessage(boolean directlyAddressed)483 public void sendDeviceToSleepAndValidateUsingStandbyMessage(boolean directlyAddressed) 484 throws Exception { 485 ITestDevice device = getDevice(); 486 WakeLockHelper.acquirePartialWakeLock(device); 487 LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice(); 488 if (directlyAddressed) { 489 hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.STANDBY); 490 } else { 491 hdmiCecClient.sendCecMessage( 492 cecClientDevice, LogicalAddress.BROADCAST, CecOperand.STANDBY); 493 } 494 waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY); 495 } 496 wakeUpDevice()497 public void wakeUpDevice() throws Exception { 498 ITestDevice device = getDevice(); 499 device.executeShellCommand("input keyevent KEYCODE_WAKEUP"); 500 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE); 501 waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON); 502 WakeLockHelper.releasePartialWakeLock(device); 503 } 504 wakeUpDeviceWithoutWait()505 public void wakeUpDeviceWithoutWait() throws Exception { 506 ITestDevice device = getDevice(); 507 device.executeShellCommand("input keyevent KEYCODE_WAKEUP"); 508 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE); 509 WakeLockHelper.releasePartialWakeLock(device); 510 } 511 checkStandbyAndWakeUp()512 public void checkStandbyAndWakeUp() throws Exception { 513 assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP); 514 wakeUpDevice(); 515 } 516 assertDeviceWakefulness(String wakefulness)517 public void assertDeviceWakefulness(String wakefulness) throws Exception { 518 ITestDevice device = getDevice(); 519 String actualWakefulness; 520 int waitTimeSeconds = 0; 521 522 do { 523 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 524 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 525 actualWakefulness = 526 device.executeShellCommand("dumpsys power | grep mWakefulness=") 527 .trim().replace("mWakefulness=", ""); 528 } while (!actualWakefulness.equals(wakefulness) 529 && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS); 530 assertWithMessage( 531 "Device wakefulness is " 532 + actualWakefulness 533 + " but expected to be " 534 + wakefulness) 535 .that(actualWakefulness) 536 .isEqualTo(wakefulness); 537 } 538 539 /** 540 * Checks a given condition once every {@link HdmiCecConstants.SLEEP_TIMESTEP_SECONDS} seconds 541 * until it is true, or {@link HdmiCecConstants.MAX_SLEEP_TIME_SECONDS} seconds have passed. 542 * Triggers an assertion failure if the condition remains false after the time limit. 543 * @param condition Callable that returns whether the condition is met 544 * @param errorMessage The message to print if the condition is false 545 */ waitForCondition(Callable<Boolean> condition, String errorMessage)546 public void waitForCondition(Callable<Boolean> condition, String errorMessage) 547 throws Exception { 548 int waitTimeSeconds = 0; 549 boolean conditionState; 550 do { 551 TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS); 552 waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS; 553 conditionState = condition.call(); 554 } while (!conditionState && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS); 555 assertWithMessage(errorMessage).that(conditionState).isTrue(); 556 } 557 sendOtp()558 public void sendOtp() throws Exception { 559 ITestDevice device = getDevice(); 560 device.executeShellCommand("cmd hdmi_control onetouchplay"); 561 } 562 setPowerControlMode(String valToSet)563 public String setPowerControlMode(String valToSet) throws Exception { 564 return setSettingsValue(POWER_CONTROL_MODE, valToSet); 565 } 566 setVolumeControlMode(String valToSet)567 public String setVolumeControlMode(String valToSet) throws Exception { 568 return setSettingsValue(SETTING_VOLUME_CONTROL_ENABLED, valToSet); 569 } 570 setPowerStateChangeOnActiveSourceLost(String valToSet)571 public String setPowerStateChangeOnActiveSourceLost(String valToSet) throws Exception { 572 return setSettingsValue(POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, valToSet); 573 } 574 setSoundbarMode(String valToSet)575 public String setSoundbarMode(String valToSet) throws Exception { 576 return setSettingsValue(SOUNDBAR_MODE, valToSet); 577 } 578 getSoundbarMode()579 public String getSoundbarMode() throws Exception { 580 return getSettingsValue(SOUNDBAR_MODE); 581 } 582 isDeviceActiveSource(ITestDevice device)583 public boolean isDeviceActiveSource(ITestDevice device) throws DumpsysParseException { 584 final String activeSource = "activeSource"; 585 final String pattern = 586 "(.*?)" 587 + "(isActiveSource\\(\\): )" 588 + "(?<" 589 + activeSource 590 + ">\\btrue\\b|\\bfalse\\b)" 591 + "(.*?)"; 592 try { 593 Pattern p = Pattern.compile(pattern); 594 String dumpsys = device.executeShellCommand("dumpsys hdmi_control"); 595 BufferedReader reader = new BufferedReader(new StringReader(dumpsys)); 596 String line; 597 while ((line = reader.readLine()) != null) { 598 Matcher matcher = p.matcher(line); 599 if (matcher.matches()) { 600 return matcher.group(activeSource).equals("true"); 601 } 602 } 603 } catch (IOException | DeviceNotAvailableException e) { 604 throw new DumpsysParseException("Could not fetch 'dumpsys hdmi_control' output.", e); 605 } 606 throw new DumpsysParseException("Could not parse isActiveSource() from dumpsys."); 607 } 608 609 /** 610 * For source devices, simulate that a sink is connected by responding to the 611 * {@code Give Power Status} message that is sent when re-enabling CEC. 612 * Validate that HdmiControlService#mIsCecAvailable is set to true as a result. 613 */ simulateCecSinkConnected(ITestDevice device, LogicalAddress source)614 public void simulateCecSinkConnected(ITestDevice device, LogicalAddress source) 615 throws Exception { 616 hdmiCecClient.clearClientOutput(); 617 device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0"); 618 waitForCondition(() -> !isCecAvailable(device), "Could not disable CEC"); 619 device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1"); 620 // When a CEC device has just become available, the CEC adapter isn't able to send it 621 // messages right away. Therefore we let the first <Give Power Status> message time-out, and 622 // only respond to the retry. 623 hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS); 624 hdmiCecClient.clearClientOutput(); 625 hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS); 626 hdmiCecClient.sendCecMessage(LogicalAddress.TV, source, CecOperand.REPORT_POWER_STATUS, 627 CecMessage.formatParams(HdmiCecConstants.CEC_POWER_STATUS_STANDBY)); 628 waitForCondition(() -> isCecAvailable(device), 629 "Simulating that a sink is connected, failed."); 630 } 631 isCecAvailable(ITestDevice device)632 boolean isCecAvailable(ITestDevice device) throws Exception { 633 return device.executeShellCommand("dumpsys hdmi_control | grep mIsCecAvailable:") 634 .replace("mIsCecAvailable:", "").trim().equals("true"); 635 } 636 isCecEnabled(ITestDevice device)637 boolean isCecEnabled(ITestDevice device) throws Exception { 638 return device.executeShellCommand("dumpsys hdmi_control | grep hdmi_cec_enabled") 639 .trim().startsWith("hdmi_cec_enabled (int): 1"); 640 } 641 642 /** 643 * Checks whether an audio output device is in the list output by an ADB shell command. 644 * Intended for checking a device's volume behavior by looking at whether it appears in 645 * a certain line of the audio dumpsys. 646 */ isAudioOutputDeviceInList(int audioOutputDevice, String deviceListAdbShellCommand)647 private boolean isAudioOutputDeviceInList(int audioOutputDevice, 648 String deviceListAdbShellCommand) throws Exception { 649 String[] splitLine = getDevice().executeShellCommand(deviceListAdbShellCommand).split("="); 650 if (splitLine.length < 2) { 651 // No devices are in the list 652 return false; 653 } 654 String[] deviceStrings = splitLine[1].trim().split(","); 655 for (String deviceString : deviceStrings) { 656 try { 657 if (Integer.decode(deviceString) == audioOutputDevice) { 658 return true; 659 } 660 } catch (NumberFormatException e) { 661 // Ignore this device and continue 662 } 663 } 664 return false; 665 } 666 667 /** 668 * Returns whether a given audio output device is currently using full volume behavior 669 */ isFullVolumeDevice(int audioOutputDevice)670 public boolean isFullVolumeDevice(int audioOutputDevice) throws Exception { 671 return isAudioOutputDeviceInList(audioOutputDevice, 672 "dumpsys audio | grep mFullVolumeDevices"); 673 } 674 675 /** 676 * Returns whether a given audio output device is currently using absolute volume behavior 677 */ isAbsoluteVolumeDevice(int audioOutputDevice)678 public boolean isAbsoluteVolumeDevice(int audioOutputDevice) throws Exception { 679 // Need to explicitly exclude the line for adjust-only absolute volume devices 680 return isAudioOutputDeviceInList(audioOutputDevice, 681 "dumpsys audio | grep 'absolute volume devices' | grep -v 'adjust-only'"); 682 } 683 684 /** 685 * Returns whether a given audio output device is currently using adjust-only absolute volume 686 * behavior 687 */ isAdjustOnlyAbsoluteVolumeDevice(int audioOutputDevice)688 public boolean isAdjustOnlyAbsoluteVolumeDevice(int audioOutputDevice) throws Exception { 689 return isAudioOutputDeviceInList(audioOutputDevice, 690 "dumpsys audio | grep 'adjust-only absolute volume devices'"); 691 } 692 } 693