• 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_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