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