• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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