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