1 /*
2  * Copyright (C) 2019 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 android.hdmicec.cts.error.CecClientWrapperException;
20 import android.hdmicec.cts.error.ErrorCodes;
21 
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.device.ITestDevice;
24 import com.android.tradefed.log.LogUtil.CLog;
25 import com.android.tradefed.util.RunUtil;
26 
27 import org.junit.rules.ExternalResource;
28 
29 import java.io.BufferedReader;
30 import java.io.BufferedWriter;
31 import java.io.File;
32 import java.io.FileReader;
33 import java.io.IOException;
34 import java.io.InputStreamReader;
35 import java.io.OutputStreamWriter;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.concurrent.TimeUnit;
40 import java.util.regex.Pattern;
41 
42 /** Class that helps communicate with the cec-client */
43 public final class HdmiCecClientWrapper extends ExternalResource {
44 
45     private static final int MILLISECONDS_TO_READY = 10000;
46     private static final int DEFAULT_TIMEOUT = 20000;
47     private static final int BUFFER_SIZE = 1024;
48 
49     private Process mCecClient;
50     private BufferedWriter mOutputConsole;
51     private BufferedReader mInputConsole;
52     private boolean mCecClientInitialised = false;
53 
54     private LogicalAddress selfDevice = LogicalAddress.RECORDER_1;
55     private LogicalAddress targetDevice = LogicalAddress.UNKNOWN;
56     private String clientParams[];
57     private StringBuilder sendVendorCommand = new StringBuilder("cmd hdmi_control vendorcommand ");
58     private int physicalAddress = 0xFFFF;
59 
60     private CecOperand featureAbortOperand = CecOperand.FEATURE_ABORT;
61     private List<Integer> featureAbortReasons =
62             new ArrayList<>(HdmiCecConstants.ABORT_INVALID_OPERAND);
63     private boolean isFeatureAbortExpected = false;
64 
65     private static final String CEC_PORT_BUSY = "unable to open the device on port";
66 
HdmiCecClientWrapper(String ....clientParams)67     public HdmiCecClientWrapper(String ...clientParams) {
68         this.clientParams = clientParams;
69     }
70 
71     @Override
after()72     protected void after() {
73         this.killCecProcess();
74     }
75 
setTargetLogicalAddress(LogicalAddress dutLogicalAddress)76     void setTargetLogicalAddress(LogicalAddress dutLogicalAddress) {
77         targetDevice = dutLogicalAddress;
78     }
79 
getValidCecClientPorts()80     public List<String> getValidCecClientPorts() throws CecClientWrapperException {
81 
82         List<String> listPortsCommand = new ArrayList();
83         Process cecClient;
84 
85         listPortsCommand.add("cec-client");
86         listPortsCommand.add("-l");
87 
88         List<String> comPorts = new ArrayList();
89         try {
90             cecClient = RunUtil.getDefault().runCmdInBackground(listPortsCommand);
91         } catch (IOException ioe) {
92             throw new CecClientWrapperException(
93                     ErrorCodes.CecClientStart,
94                     "as cec-client may not be installed. Please refer to README for"
95                         + " setup/installation instructions.");
96         }
97         try {
98             BufferedReader inputConsole =
99                     new BufferedReader(new InputStreamReader(cecClient.getInputStream()));
100             while (cecClient.isAlive()) {
101                 if (inputConsole.ready()) {
102                     String line = inputConsole.readLine();
103                     if (line.toLowerCase().contains("com port")) {
104                         String port = line.split(":")[1].trim();
105                         comPorts.add(port);
106                     }
107                 }
108             }
109             inputConsole.close();
110             cecClient.waitFor();
111         } catch (IOException | InterruptedException ioe) {
112             throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
113         }
114 
115         return comPorts;
116     }
117 
initValidCecClient(ITestDevice device, List<String> clientCommands)118     boolean initValidCecClient(ITestDevice device, List<String> clientCommands)
119             throws CecClientWrapperException {
120 
121         String serialNo;
122         List<String> launchCommand = new ArrayList(clientCommands);
123         try {
124             serialNo = device.getProperty("ro.serialno");
125         } catch (DeviceNotAvailableException de) {
126             throw new CecClientWrapperException(ErrorCodes.DeviceNotAvailable, de);
127         }
128         File mDeviceEntry = new File(HdmiCecConstants.CEC_MAP_FOLDER, serialNo);
129 
130         try (BufferedReader reader = new BufferedReader(new FileReader(mDeviceEntry))) {
131             String port = reader.readLine();
132             launchCommand.add(port);
133             mCecClient = RunUtil.getDefault().runCmdInBackground(launchCommand);
134             mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream()));
135 
136             /* Wait for the client to become ready */
137             if (checkConsoleOutput(
138                     CecClientMessage.CLIENT_CONSOLE_READY + "", MILLISECONDS_TO_READY)) {
139                         mOutputConsole =
140                                 new BufferedWriter(
141                                         new OutputStreamWriter(mCecClient.getOutputStream()),
142                                         BUFFER_SIZE);
143                         return true;
144             } else {
145                 CLog.e("Console did not get ready!");
146                 /* Kill the unwanted cec-client process. */
147                 Process killProcess = mCecClient.destroyForcibly();
148                 killProcess.waitFor();
149             }
150         } catch (IOException | InterruptedException ioe) {
151             throw new CecClientWrapperException(
152                     ErrorCodes.ReadConsole, ioe, "Could not open port mapping file");
153         }
154         return false;
155     }
156 
157     /** Initialise the client */
init(boolean startAsTv, ITestDevice device)158     void init(boolean startAsTv, ITestDevice device) throws CecClientWrapperException {
159         if (targetDevice == LogicalAddress.UNKNOWN) {
160             throw new CecClientWrapperException(
161                     ErrorCodes.CecClientStart, "Missing logical address of the target device.");
162         }
163 
164         List<String> commands = new ArrayList();
165 
166         commands.add("cec-client");
167 
168         /* "-p 2" starts the client as if it is connected to HDMI port 2, taking the physical
169          * address 2.0.0.0 */
170         commands.add("-p");
171         commands.add("2");
172         physicalAddress = 0x2000;
173         if (startAsTv) {
174             commands.add("-t");
175             commands.add("x");
176             selfDevice = LogicalAddress.TV;
177         }
178         /* "-d 15" set the log level to ERROR|WARNING|NOTICE|TRAFFIC */
179         commands.add("-d");
180         commands.add("15");
181         commands.addAll(Arrays.asList(clientParams));
182         if (Arrays.asList(clientParams).contains("a")) {
183             selfDevice = LogicalAddress.AUDIO_SYSTEM;
184         }
185 
186         mCecClientInitialised = true;
187         if (!initValidCecClient(device, commands)) {
188             mCecClientInitialised = false;
189 
190             throw new CecClientWrapperException(ErrorCodes.CecClientStart);
191         }
192     }
193 
checkCecClient()194     private void checkCecClient() throws CecClientWrapperException {
195         if (!mCecClientInitialised) {
196             throw new CecClientWrapperException(ErrorCodes.CecClientStart);
197         }
198         if (!mCecClient.isAlive()) {
199             throw new CecClientWrapperException(ErrorCodes.CecClientNotRunning);
200         }
201     }
202 
203     /**
204      * Sends a CEC message with source marked as broadcast to the device passed in the constructor
205      * through the output console of the cec-communication channel.
206      */
sendCecMessage(CecOperand message)207     public void sendCecMessage(CecOperand message) throws CecClientWrapperException {
208         sendCecMessage(message, "");
209     }
210 
211     /**
212      * Sends a CEC message with source marked as broadcast to the device passed in the constructor
213      * through the output console of the cec-communication channel.
214      */
sendCecMessage(CecOperand message, String params)215     public void sendCecMessage(CecOperand message, String params) throws CecClientWrapperException {
216         sendCecMessage(LogicalAddress.BROADCAST, targetDevice, message, params);
217     }
218 
219     /**
220      * Sends a CEC message from source device to the device passed in the constructor through the
221      * output console of the cec-communication channel.
222      */
sendCecMessage(LogicalAddress source, CecOperand message)223     public void sendCecMessage(LogicalAddress source, CecOperand message)
224             throws CecClientWrapperException {
225         sendCecMessage(source, targetDevice, message, "");
226     }
227 
228     /**
229      * Sends a CEC message from source device to the device passed in the constructor through the
230      * output console of the cec-communication channel with the appended params.
231      */
sendCecMessage(LogicalAddress source, CecOperand message, String params)232     public void sendCecMessage(LogicalAddress source, CecOperand message, String params)
233             throws Exception {
234         sendCecMessage(source, targetDevice, message, params);
235     }
236 
237     /**
238      * Sends a CEC message from source device to a destination device through the output console of
239      * the cec-communication channel.
240      */
sendCecMessage( LogicalAddress source, LogicalAddress destination, CecOperand message)241     public void sendCecMessage(
242             LogicalAddress source, LogicalAddress destination, CecOperand message)
243             throws CecClientWrapperException {
244         sendCecMessage(source, destination, message, "");
245     }
246 
247     /**
248      * Broadcasts a CEC ACTIVE_SOURCE message from client device source through the output console
249      * of the cec-communication channel.
250      */
broadcastActiveSource(LogicalAddress source)251     public void broadcastActiveSource(LogicalAddress source) throws CecClientWrapperException {
252         int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF;
253         sendCecMessage(
254                 source,
255                 LogicalAddress.BROADCAST,
256                 CecOperand.ACTIVE_SOURCE,
257                 CecMessage.formatParams(sourcePa, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
258     }
259 
260     /**
261      * Broadcasts a CEC ACTIVE_SOURCE message with physicalAddressOfActiveDevice from client device
262      * source through the output console of the cec-communication channel.
263      */
broadcastActiveSource(LogicalAddress source, int physicalAddressOfActiveDevice)264     public void broadcastActiveSource(LogicalAddress source, int physicalAddressOfActiveDevice)
265             throws CecClientWrapperException {
266         sendCecMessage(
267                 source,
268                 LogicalAddress.BROADCAST,
269                 CecOperand.ACTIVE_SOURCE,
270                 CecMessage.formatParams(
271                         physicalAddressOfActiveDevice, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
272     }
273 
274     /**
275      * Broadcasts a CEC REPORT_PHYSICAL_ADDRESS message from client device source through the output
276      * console of the cec-communication channel.
277      */
broadcastReportPhysicalAddress(LogicalAddress source)278     public void broadcastReportPhysicalAddress(LogicalAddress source)
279             throws CecClientWrapperException {
280         String deviceType = CecMessage.formatParams(source.getDeviceType());
281         int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF;
282         String physicalAddress =
283                 CecMessage.formatParams(sourcePa, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
284         sendCecMessage(
285                 source,
286                 LogicalAddress.BROADCAST,
287                 CecOperand.REPORT_PHYSICAL_ADDRESS,
288                 physicalAddress + deviceType);
289     }
290 
291     /**
292      * Broadcasts a CEC REPORT_PHYSICAL_ADDRESS message with physicalAddressToReport from client
293      * device source through the output console of the cec-communication channel.
294      */
broadcastReportPhysicalAddress(LogicalAddress source, int physicalAddressToReport)295     public void broadcastReportPhysicalAddress(LogicalAddress source, int physicalAddressToReport)
296             throws CecClientWrapperException {
297         String deviceType = CecMessage.formatParams(source.getDeviceType());
298         String physicalAddress =
299                 CecMessage.formatParams(
300                         physicalAddressToReport, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
301         sendCecMessage(
302                 source,
303                 LogicalAddress.BROADCAST,
304                 CecOperand.REPORT_PHYSICAL_ADDRESS,
305                 physicalAddress + deviceType);
306     }
307 
308     /**
309      * Sends a CEC message from source device to a destination device through the output console of
310      * the cec-communication channel with the appended params.
311      */
sendCecMessage( LogicalAddress source, LogicalAddress destination, CecOperand message, String params)312     public void sendCecMessage(
313             LogicalAddress source, LogicalAddress destination, CecOperand message, String params)
314             throws CecClientWrapperException {
315         checkCecClient();
316         String sendMessageString = "tx " + source + destination + ":" + message + params;
317         try {
318             CLog.v("Sending CEC message: " + sendMessageString);
319             mOutputConsole.write(sendMessageString);
320             mOutputConsole.newLine();
321             mOutputConsole.flush();
322         } catch (IOException ioe) {
323             throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
324         }
325     }
326 
sendMultipleUserControlPressAndRelease( LogicalAddress source, List<Integer> keycodes)327     public void sendMultipleUserControlPressAndRelease(
328             LogicalAddress source, List<Integer> keycodes) throws CecClientWrapperException {
329         try {
330             for (int keycode : keycodes) {
331                 String key = String.format("%02x", keycode);
332                 mOutputConsole.write(
333                         "tx "
334                                 + source
335                                 + targetDevice
336                                 + ":"
337                                 + CecOperand.USER_CONTROL_PRESSED
338                                 + ":"
339                                 + key);
340                 mOutputConsole.newLine();
341                 mOutputConsole.write(
342                         "tx " + source + targetDevice + ":" + CecOperand.USER_CONTROL_RELEASED);
343                 mOutputConsole.newLine();
344                 mOutputConsole.flush();
345                 TimeUnit.MILLISECONDS.sleep(200);
346             }
347         } catch (InterruptedException | IOException ioe) {
348             throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
349         }
350     }
351 
352     /**
353      * Sends a <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> from source to device through the
354      * output console of the cec-communication channel with the mentioned keycode.
355      */
sendUserControlPressAndRelease(LogicalAddress source, int keycode, boolean holdKey)356     public void sendUserControlPressAndRelease(LogicalAddress source, int keycode, boolean holdKey)
357             throws CecClientWrapperException {
358         sendUserControlPressAndRelease(source, targetDevice, keycode, holdKey);
359     }
360 
361     /**
362      * Sends a <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> from source to destination
363      * through the output console of the cec-communication channel with the mentioned keycode.
364      */
sendUserControlPressAndRelease( LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey)365     public void sendUserControlPressAndRelease(
366             LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey)
367             throws CecClientWrapperException {
368         sendUserControlPress(source, destination, keycode, holdKey);
369         try {
370             /* Sleep less than 200ms between press and release */
371             TimeUnit.MILLISECONDS.sleep(100);
372             mOutputConsole.write(
373                     "tx " + source + destination + ":" + CecOperand.USER_CONTROL_RELEASED);
374             mOutputConsole.flush();
375         } catch (IOException | InterruptedException ioe) {
376             throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
377         }
378     }
379 
380     /**
381      * Sends a {@code <UCP>} with and additional param. This is used to check that the DUT ignores
382      * additional params in an otherwise correct message.
383      */
sendUserControlPressAndReleaseWithAdditionalParams( LogicalAddress source, LogicalAddress destination, int keyCode, int additionalParam)384     public void sendUserControlPressAndReleaseWithAdditionalParams(
385             LogicalAddress source, LogicalAddress destination, int keyCode, int additionalParam)
386             throws CecClientWrapperException {
387         String key = String.format("%02x", keyCode);
388         String command =
389                 "tx "
390                         + source
391                         + destination
392                         + ":"
393                         + CecOperand.USER_CONTROL_PRESSED
394                         + ":"
395                         + key
396                         + ":"
397                         + additionalParam;
398 
399         try {
400             mOutputConsole.write(command);
401             mOutputConsole.newLine();
402             mOutputConsole.write(
403                     "tx " + source + destination + ":" + CecOperand.USER_CONTROL_RELEASED);
404             mOutputConsole.newLine();
405             mOutputConsole.flush();
406         } catch (IOException ioe) {
407             throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
408         }
409     }
410 
411     /**
412      * Sends a <UCP> message from source to destination through the output console of the
413      * cec-communication channel with the mentioned keycode. If holdKey is true, the method will
414      * send multiple <UCP> messages to simulate a long press. No <UCR> will be sent.
415      */
sendUserControlPress( LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey)416     public void sendUserControlPress(
417             LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey)
418             throws CecClientWrapperException {
419         String key = String.format("%02x", keycode);
420         String command = "tx " + source + destination + ":" +
421                 CecOperand.USER_CONTROL_PRESSED + ":" + key;
422 
423         try {
424             if (holdKey) {
425                 /* Repeat once every 450ms for at least 5 seconds. Send 11 times in loop every
426                  * 450ms. The message is sent once after the loop as well.
427                  * ((11 + 1) * 0.45 = 5.4s total) */
428                 int repeat = 11;
429                 for (int i = 0; i < repeat; i++) {
430                     mOutputConsole.write(command);
431                     mOutputConsole.newLine();
432                     mOutputConsole.flush();
433                     TimeUnit.MILLISECONDS.sleep(450);
434                 }
435             }
436 
437             mOutputConsole.write(command);
438             mOutputConsole.newLine();
439             mOutputConsole.flush();
440         } catch (IOException | InterruptedException ioe) {
441             throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
442         }
443     }
444 
445     /**
446      * Sends a series of <UCP> [firstKeycode] from source to destination through the output console
447      * of the cec-communication channel immediately followed by <UCP> [secondKeycode]. No <UCR>
448      * message is sent.
449      */
sendUserControlInterruptedPressAndHold( LogicalAddress source, LogicalAddress destination, int firstKeycode, int secondKeycode, boolean holdKey)450     public void sendUserControlInterruptedPressAndHold(
451             LogicalAddress source,
452             LogicalAddress destination,
453             int firstKeycode,
454             int secondKeycode,
455             boolean holdKey)
456             throws CecClientWrapperException {
457         sendUserControlPress(source, destination, firstKeycode, holdKey);
458         try {
459             /* Sleep less than 200ms between press and release */
460             TimeUnit.MILLISECONDS.sleep(100);
461         } catch (InterruptedException ie) {
462             throw new CecClientWrapperException(ErrorCodes.WriteConsole, ie);
463         }
464         sendUserControlPress(source, destination, secondKeycode, false);
465     }
466 
467     /** Sends a poll message to the device */
sendPoll()468     public void sendPoll() throws Exception {
469         sendPoll(targetDevice);
470     }
471 
472     /** Sends a poll message to the destination */
sendPoll(LogicalAddress destination)473     public void sendPoll(LogicalAddress destination) throws Exception {
474         String command = CecClientMessage.POLL + " " + destination;
475         sendConsoleMessage(command);
476     }
477 
478 
479     /** Sends a message to the output console of the cec-client */
sendConsoleMessage(String message)480     public void sendConsoleMessage(String message) throws CecClientWrapperException {
481         sendConsoleMessage(message, mOutputConsole);
482     }
483 
484     /** Sends a message to the output console of the cec-client */
sendConsoleMessage(String message, BufferedWriter outputConsole)485     public void sendConsoleMessage(String message, BufferedWriter outputConsole)
486             throws CecClientWrapperException {
487         CLog.v("Sending console message:: " + message);
488         try {
489             outputConsole.write(message);
490             outputConsole.flush();
491         } catch (IOException ioe) {
492             throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
493         }
494     }
495 
496     /** Check for any string on the input console of the cec-client, uses default timeout */
checkConsoleOutput(String expectedMessage)497     public boolean checkConsoleOutput(String expectedMessage) throws CecClientWrapperException {
498         return checkConsoleOutput(expectedMessage, DEFAULT_TIMEOUT);
499     }
500 
501     /** Check for any string on the input console of the cec-client */
checkConsoleOutput(String expectedMessage, long timeoutMillis)502     public boolean checkConsoleOutput(String expectedMessage, long timeoutMillis)
503             throws CecClientWrapperException {
504         checkCecClient();
505         return checkConsoleOutput(expectedMessage, timeoutMillis, mInputConsole);
506     }
507 
508     /** Check for any string on the specified input console */
checkConsoleOutput( String expectedMessage, long timeoutMillis, BufferedReader inputConsole)509     public boolean checkConsoleOutput(
510             String expectedMessage, long timeoutMillis, BufferedReader inputConsole)
511             throws CecClientWrapperException {
512         long startTime = System.currentTimeMillis();
513         long endTime = startTime;
514 
515         while ((endTime - startTime <= timeoutMillis)) {
516             try {
517                 if (inputConsole.ready()) {
518                     String line = inputConsole.readLine();
519                     if (line != null
520                             && line.toLowerCase().contains(expectedMessage.toLowerCase())) {
521                         CLog.v("Found " + expectedMessage + " in " + line);
522                         return true;
523                     } else if (line.toLowerCase().contains(CEC_PORT_BUSY.toLowerCase())) {
524                         throw new CecClientWrapperException(ErrorCodes.CecPortBusy);
525                     }
526                 }
527             } catch (IOException ioe) {
528                 throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
529             }
530             endTime = System.currentTimeMillis();
531         }
532         return false;
533     }
534 
535     /** Gets all the messages received from the given list of source devices during a period of
536      * duration seconds.
537      */
getAllMessages(List<LogicalAddress> sourceList, int duration)538     public List<CecOperand> getAllMessages(List<LogicalAddress> sourceList, int duration)
539             throws CecClientWrapperException {
540         List<CecOperand> receivedOperands = new ArrayList<>();
541         long startTime = System.currentTimeMillis();
542         long endTime = startTime;
543 
544         String source = sourceList.toString().replace(",", "").replace(" ", "");
545 
546         Pattern pattern = Pattern.compile("(.*>>)(.*?)" +
547                 "(" + source + "\\p{XDigit}):(.*)",
548             Pattern.CASE_INSENSITIVE);
549 
550         while ((endTime - startTime <= (duration * 1000))) {
551             try {
552                 if (mInputConsole.ready()) {
553                     String line = mInputConsole.readLine();
554                     if (pattern.matcher(line).matches()) {
555                         CecOperand operand = CecMessage.getOperand(line);
556                         if (!receivedOperands.contains(operand)) {
557                             receivedOperands.add(operand);
558                         }
559                     }
560                 }
561             } catch (IOException ioe) {
562                 throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
563             }
564             endTime = System.currentTimeMillis();
565         }
566         return receivedOperands;
567     }
568 
569     /**
570      * Gets the list of logical addresses which receives messages with operand expectedMessage
571      * during a period of duration seconds.
572      */
getAllDestLogicalAddresses(CecOperand expectedMessage, int duration)573     public List<LogicalAddress> getAllDestLogicalAddresses(CecOperand expectedMessage, int duration)
574             throws CecClientWrapperException {
575         return getAllDestLogicalAddresses(expectedMessage, "", duration);
576     }
577 
578     /**
579      * Gets the list of logical addresses which receives messages with operand expectedMessage and
580      * params during a period of duration seconds.
581      */
getAllDestLogicalAddresses( CecOperand expectedMessage, String params, int duration)582     public List<LogicalAddress> getAllDestLogicalAddresses(
583             CecOperand expectedMessage, String params, int duration)
584             throws CecClientWrapperException {
585         List<LogicalAddress> destinationAddresses = new ArrayList<>();
586         long startTime = System.currentTimeMillis();
587         long endTime = startTime;
588         Pattern pattern =
589                 Pattern.compile(
590                         "(.*>>)(.*?)" + ":(" + expectedMessage + params + ")(.*)",
591                         Pattern.CASE_INSENSITIVE);
592 
593         while ((endTime - startTime <= (duration * 1000))) {
594             try {
595                 if (mInputConsole.ready()) {
596                     String line = mInputConsole.readLine();
597                     if (pattern.matcher(line).matches()) {
598                         LogicalAddress destination = CecMessage.getDestination(line);
599                         if (!destinationAddresses.contains(destination)) {
600                             destinationAddresses.add(destination);
601                         }
602                     }
603                 }
604             } catch (IOException ioe) {
605                 throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
606             }
607             endTime = System.currentTimeMillis();
608         }
609         return destinationAddresses;
610     }
611 
612     /**
613      * The next checkExpectedOutput calls will also permit a feature abort as an alternate to the
614      * expected operand. The feature abort will be permissible if it has
615      *
616      * @param abortForOperand The operand for which the feature abort could be an allowed response
617      * @param reasons List of allowed reasons that the feature abort message could have
618      */
setExpectFeatureAbortFor(CecOperand abortOperand, Integer... abortReasons)619     private void setExpectFeatureAbortFor(CecOperand abortOperand, Integer... abortReasons) {
620         isFeatureAbortExpected = true;
621         featureAbortOperand = abortOperand;
622         featureAbortReasons = Arrays.asList(abortReasons);
623     }
624 
625     /** Removes feature abort as a permissible alternate response for {@link checkExpectedOutput} */
unsetExpectFeatureAbort()626     private void unsetExpectFeatureAbort() {
627         isFeatureAbortExpected = false;
628         CecOperand featureAbortOperand = CecOperand.FEATURE_ABORT;
629         List<Integer> featureAbortReasons = new ArrayList<>(HdmiCecConstants.ABORT_INVALID_OPERAND);
630     }
631 
632     /**
633      * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and
634      * returns the first line that contains that message within default timeout. If the CEC message
635      * is not found within the timeout, an CecClientWrapperException is thrown.
636      */
checkExpectedOutput(CecOperand expectedMessage)637     public String checkExpectedOutput(CecOperand expectedMessage) throws CecClientWrapperException {
638         return checkExpectedOutput(
639                 targetDevice, LogicalAddress.BROADCAST, expectedMessage, DEFAULT_TIMEOUT, false);
640     }
641 
642     /**
643      * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client communication
644      * channel and returns the first line that contains that message within default timeout. If the
645      * CEC message is not found within the timeout, an CecClientWrapperException is thrown.
646      */
checkExpectedOutput(LogicalAddress toDevice, CecOperand expectedMessage)647     public String checkExpectedOutput(LogicalAddress toDevice, CecOperand expectedMessage)
648             throws CecClientWrapperException {
649         return checkExpectedOutput(targetDevice, toDevice, expectedMessage, DEFAULT_TIMEOUT, false);
650     }
651 
652     /**
653      * Looks for the broadcasted CEC expectedMessage sent from cec-client device fromDevice on the
654      * cec-client communication channel and returns the first line that contains that message within
655      * default timeout. If the CEC message is not found within the timeout, an
656      * CecClientWrapperException is thrown.
657      */
checkExpectedMessageFromClient( LogicalAddress fromDevice, CecOperand expectedMessage)658     public String checkExpectedMessageFromClient(
659             LogicalAddress fromDevice, CecOperand expectedMessage)
660             throws CecClientWrapperException {
661         return checkExpectedMessageFromClient(
662                 fromDevice, LogicalAddress.BROADCAST, expectedMessage);
663     }
664 
665     /**
666      * Looks for the CEC expectedMessage sent from cec-client device fromDevice to CEC device
667      * toDevice on the cec-client communication channel and returns the first line that contains
668      * that message within default timeout. If the CEC message is not found within the timeout, an
669      * CecClientWrapperException is thrown.
670      */
checkExpectedMessageFromClient( LogicalAddress fromDevice, LogicalAddress toDevice, CecOperand expectedMessage)671     public String checkExpectedMessageFromClient(
672             LogicalAddress fromDevice, LogicalAddress toDevice, CecOperand expectedMessage)
673             throws CecClientWrapperException {
674         return checkExpectedOutput(fromDevice, toDevice, expectedMessage, DEFAULT_TIMEOUT, true);
675     }
676 
677     /**
678      * Looks for the CEC expectedMessage or a {@code <Feature Abort>} for {@code
679      * featureAbortOperand} with one of the abort reasons in {@code abortReason} is sent from
680      * cec-client device fromDevice to the DUT on the cec-client communication channel and returns
681      * the first line that contains that message within default timeout. If the CEC message is not
682      * found within the timeout, a CecClientWrapperException is thrown.
683      */
checkExpectedOutputOrFeatureAbort( LogicalAddress fromDevice, CecOperand expectedMessage, CecOperand featureAbortOperand, Integer... featureAbortReasons)684     public String checkExpectedOutputOrFeatureAbort(
685             LogicalAddress fromDevice,
686             CecOperand expectedMessage,
687             CecOperand featureAbortOperand,
688             Integer... featureAbortReasons)
689             throws CecClientWrapperException {
690         setExpectFeatureAbortFor(featureAbortOperand, featureAbortReasons);
691         String message =
692                 checkExpectedOutput(
693                         targetDevice, fromDevice, expectedMessage, DEFAULT_TIMEOUT, false);
694         unsetExpectFeatureAbort();
695         return message;
696     }
697 
698     /**
699      * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and
700      * returns the first line that contains that message within timeoutMillis. If the CEC message is
701      * not found within the timeout, an CecClientWrapperException is thrown.
702      */
checkExpectedOutput(CecOperand expectedMessage, long timeoutMillis)703     public String checkExpectedOutput(CecOperand expectedMessage, long timeoutMillis)
704             throws CecClientWrapperException {
705         return checkExpectedOutput(
706                 targetDevice, LogicalAddress.BROADCAST, expectedMessage, timeoutMillis, false);
707     }
708 
709     /**
710      * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client communication
711      * channel and returns the first line that contains that message within timeoutMillis. If the
712      * CEC message is not found within the timeout, an CecClientWrapperException is thrown.
713      */
checkExpectedOutput( LogicalAddress toDevice, CecOperand expectedMessage, long timeoutMillis)714     public String checkExpectedOutput(
715             LogicalAddress toDevice, CecOperand expectedMessage, long timeoutMillis)
716             throws CecClientWrapperException {
717         return checkExpectedOutput(targetDevice, toDevice, expectedMessage, timeoutMillis, false);
718     }
719 
720     /**
721      * Looks for the CEC expectedMessage sent from CEC device fromDevice to CEC device toDevice on
722      * the cec-client communication channel and returns the first line that contains that message
723      * within default timeout. If the CEC message is not found within the timeout, a
724      * CecClientWrapperException is thrown.
725      */
checkExpectedOutput( LogicalAddress fromDevice, LogicalAddress toDevice, CecOperand expectedMessage)726     public String checkExpectedOutput(
727             LogicalAddress fromDevice, LogicalAddress toDevice, CecOperand expectedMessage)
728             throws CecClientWrapperException {
729         return checkExpectedOutput(fromDevice, toDevice, expectedMessage, DEFAULT_TIMEOUT, false);
730     }
731 
732     /**
733      * Looks for the CEC expectedMessage sent from CEC device fromDevice to CEC device toDevice on
734      * the cec-client communication channel and returns the first line that contains that message
735      * within timeoutMillis. If the CEC message is not found within the timeout, an
736      * CecClientWrapperException is thrown. This method looks for the CEC messages coming from
737      * Cec-client if fromCecClient is true.
738      */
checkExpectedOutput( LogicalAddress fromDevice, LogicalAddress toDevice, CecOperand expectedMessage, long timeoutMillis, boolean fromCecClient)739     public String checkExpectedOutput(
740             LogicalAddress fromDevice,
741             LogicalAddress toDevice,
742             CecOperand expectedMessage,
743             long timeoutMillis,
744             boolean fromCecClient)
745             throws CecClientWrapperException {
746         checkCecClient();
747         long startTime = System.currentTimeMillis();
748         long endTime = startTime;
749         String direction = fromCecClient ? "<<" : ">>";
750         Pattern pattern;
751         if (expectedMessage == CecOperand.POLL) {
752             pattern =
753                     Pattern.compile(
754                             "(.*"
755                                     + direction
756                                     + ")(.*?)"
757                                     + "("
758                                     + fromDevice
759                                     + toDevice
760                                     + ")(.*)",
761                             Pattern.CASE_INSENSITIVE);
762         } else {
763             String expectedOperands = expectedMessage.toString();
764             if (isFeatureAbortExpected) {
765                 expectedOperands += "|" + CecOperand.FEATURE_ABORT;
766             }
767             pattern =
768                     Pattern.compile(
769                             "(.*"
770                                     + direction
771                                     + ")(.*?)"
772                                     + "("
773                                     + fromDevice
774                                     + toDevice
775                                     + "):"
776                                     + "("
777                                     + expectedOperands
778                                     + ")(.*)",
779                             Pattern.CASE_INSENSITIVE);
780         }
781         while ((endTime - startTime <= timeoutMillis)) {
782             try {
783                 if (mInputConsole.ready()) {
784                     String line = mInputConsole.readLine();
785                     if (pattern.matcher(line).matches()) {
786                         if (isFeatureAbortExpected
787                                 && CecMessage.getOperand(line) == CecOperand.FEATURE_ABORT) {
788                             CecOperand featureAbortedFor =
789                                     CecOperand.getOperand(CecMessage.getParams(line, 0, 2));
790                             int reason = CecMessage.getParams(line, 2, 4);
791                             if (featureAbortedFor == featureAbortOperand
792                                     && featureAbortReasons.contains(reason)) {
793                                 return line;
794                             } else {
795                                 continue;
796                             }
797                         }
798                         CLog.v("Found " + expectedMessage.name() + " in " + line);
799                         return line;
800                     }
801                 }
802             } catch (IOException ioe) {
803                 throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
804             }
805             endTime = System.currentTimeMillis();
806         }
807         throw new CecClientWrapperException(ErrorCodes.CecMessageNotFound, expectedMessage.name());
808     }
809 
checkNoMessagesSentFromDevice(int timeoutMillis, List<CecOperand> excludeOperands)810     public void checkNoMessagesSentFromDevice(int timeoutMillis, List<CecOperand> excludeOperands)
811             throws CecClientWrapperException {
812         checkCecClient();
813         long startTime = System.currentTimeMillis();
814         long endTime = startTime;
815         Pattern pattern =
816                 Pattern.compile("(.*>>)(.*?)("
817                                 + targetDevice
818                                 + "\\p{XDigit}):(.*)",
819                         Pattern.CASE_INSENSITIVE);
820         while ((endTime - startTime <= timeoutMillis)) {
821             try {
822                 if (mInputConsole.ready()) {
823                     String line = mInputConsole.readLine();
824                     if (pattern.matcher(line).matches()) {
825                         CecOperand operand = CecMessage.getOperand(line);
826                         if(excludeOperands.contains(operand)){
827                             continue;
828                         }
829                         CLog.v("Found unexpected message in " + line);
830                         throw new CecClientWrapperException(
831                                 ErrorCodes.CecMessageFound,
832                                 CecMessage.getOperand(line)
833                                         + " from "
834                                         + targetDevice
835                                         + " with params "
836                                         + CecMessage.getParamsAsString(line));
837                     }
838                 }
839             } catch (IOException ioe) {
840                 throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
841             }
842             endTime = System.currentTimeMillis();
843         }
844     }
845 
checkNoMessagesSentFromDevice(int timeoutMillis)846     public void checkNoMessagesSentFromDevice(int timeoutMillis)
847             throws CecClientWrapperException {
848         List<CecOperand> excludeOperands = new ArrayList<>();
849         checkNoMessagesSentFromDevice(timeoutMillis, excludeOperands);
850     }
851 
852     /**
853      * Looks for the CEC message incorrectMessage sent to CEC device toDevice on the cec-client
854      * communication channel and throws an CecClientWrapperException if it finds the line that
855      * contains the message within the default timeout. If the CEC message is not found within the
856      * timeout, function returns without error.
857      */
checkOutputDoesNotContainMessage( LogicalAddress toDevice, CecOperand incorrectMessage)858     public void checkOutputDoesNotContainMessage(
859             LogicalAddress toDevice, CecOperand incorrectMessage) throws CecClientWrapperException {
860         checkOutputDoesNotContainMessage(toDevice, incorrectMessage, "", DEFAULT_TIMEOUT);
861      }
862 
863     /**
864      * Looks for the CEC message incorrectMessage along with the params sent to CEC device toDevice
865      * on the cec-client communication channel and throws an CecClientWrapperException if it finds
866      * the line that contains the message with its params within the default timeout. If the CEC
867      * message is not found within the timeout, function returns without error.
868      */
checkOutputDoesNotContainMessage( LogicalAddress toDevice, CecOperand incorrectMessage, String params)869     public void checkOutputDoesNotContainMessage(
870             LogicalAddress toDevice, CecOperand incorrectMessage, String params)
871             throws CecClientWrapperException {
872         checkOutputDoesNotContainMessage(toDevice, incorrectMessage, params, DEFAULT_TIMEOUT);
873     }
874 
875     /**
876      * Looks for the CEC message incorrectMessage sent to CEC device toDevice on the cec-client
877      * communication channel and throws an CecClientWrapperException if it finds the line that
878      * contains the message within timeoutMillis. If the CEC message is not found within the
879      * timeout, function returns without error.
880      */
checkOutputDoesNotContainMessage( LogicalAddress toDevice, CecOperand incorrectMessage, long timeoutMillis)881     public void checkOutputDoesNotContainMessage(
882             LogicalAddress toDevice, CecOperand incorrectMessage, long timeoutMillis)
883             throws CecClientWrapperException {
884         checkOutputDoesNotContainMessage(toDevice, incorrectMessage, "", timeoutMillis);
885     }
886 
887     /**
888      * Looks for the CEC message incorrectMessage along with the params sent to CEC device toDevice
889      * on the cec-client communication channel and throws an CecClientWrapperException if it finds
890      * the line that contains the message and params within timeoutMillis. If the CEC message is not
891      * found within the timeout, function returns without error.
892      */
checkOutputDoesNotContainMessage( LogicalAddress toDevice, CecOperand incorrectMessage, String params, long timeoutMillis)893     public void checkOutputDoesNotContainMessage(
894             LogicalAddress toDevice, CecOperand incorrectMessage, String params, long timeoutMillis)
895             throws CecClientWrapperException {
896         checkCecClient();
897         long startTime = System.currentTimeMillis();
898         long endTime = startTime;
899         Pattern pattern =
900                 Pattern.compile(
901                         "(.*>>)(.*?)"
902                                 + "("
903                                 + targetDevice
904                                 + toDevice
905                                 + "):"
906                                 + "("
907                                 + incorrectMessage
908                                 + params
909                                 + ")(.*)",
910                         Pattern.CASE_INSENSITIVE);
911 
912         while ((endTime - startTime <= timeoutMillis)) {
913             try {
914                 if (mInputConsole.ready()) {
915                     String line = mInputConsole.readLine();
916                     if (pattern.matcher(line).matches()) {
917                         CLog.v("Found " + incorrectMessage.name() + " in " + line);
918                         throw new CecClientWrapperException(
919                                 ErrorCodes.CecMessageFound,
920                                 incorrectMessage.name()
921                                         + " to "
922                                         + toDevice
923                                         + " with params "
924                                         + CecMessage.getParamsAsString(line));
925                     }
926                 }
927             } catch (IOException ioe) {
928                 throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
929             }
930             endTime = System.currentTimeMillis();
931         }
932      }
933 
934     /**
935      * Checks that one of the message from the {@code primaryMessages} is broadcasted from target
936      * device before sending any of the messages from the {@code secondaryMessages} on the
937      * cec-client communication channel within default time.
938      *
939      * @param primaryMessages   list of CEC messages out of which at least one is expected from the
940      *                          target device.
941      * @param secondaryMessages list of CEC messages that are not expected before primary messages
942      *                          to be sent from the target device.
943      * @return the first line that contains any of the primaryMessages.
944      * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
945      * are found, exception is thrown.
946      */
checkMessagesInOrder( List<CecOperand> primaryMessages, List<String> secondaryMessages)947     public String checkMessagesInOrder(
948             List<CecOperand> primaryMessages,
949             List<String> secondaryMessages)
950             throws CecClientWrapperException {
951         return checkMessagesInOrder(LogicalAddress.BROADCAST, primaryMessages, secondaryMessages);
952     }
953 
954     /**
955      * Checks that one of the message from the {@code primaryMessages} is sent from target
956      * device to destination before sending any of the messages from the {@code secondaryMessages}
957      * on the cec-client communication channel within default time.
958      *
959      * @param destination       logical address of the destination device.
960      * @param primaryMessages   list of CEC messages out of which at least one is expected from the
961      *                          target device.
962      * @param secondaryMessages list of CEC messages that are not expected before primary messages
963      *                          to be sent from the target device.
964      * @return the first line that contains any of the primaryMessages.
965      * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
966      * are found, exception is thrown.
967      */
checkMessagesInOrder( LogicalAddress destination, List<CecOperand> primaryMessages, List<String> secondaryMessages)968     public String checkMessagesInOrder(
969             LogicalAddress destination,
970             List<CecOperand> primaryMessages,
971             List<String> secondaryMessages)
972             throws CecClientWrapperException {
973         return checkMessagesInOrder(
974                 destination, primaryMessages, secondaryMessages, DEFAULT_TIMEOUT);
975     }
976 
977     /**
978      * Checks that one of the message from the {@code primaryMessages} is sent from target
979      * device to destination before sending any of the messages from the {@code secondaryMessages}
980      * on the cec-client communication channel within give time.
981      *
982      * @param destination       logical address of the destination device.
983      * @param primaryMessages   list of CEC messages out of which at least one is expected from the
984      *                          target device.
985      * @param secondaryMessages list of CEC messages that are not expected before primary messages
986      *                          to be sent from the target device.
987      * @param timeoutMillis     timeout to monitor CEC messages from source device.
988      * @return the first line that contains any of the primaryMessages.
989      * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
990      * are found, exception is thrown.
991      */
checkMessagesInOrder( LogicalAddress destination, List<CecOperand> primaryMessages, List<String> secondaryMessages, long timeoutMillis)992     public String checkMessagesInOrder(
993             LogicalAddress destination,
994             List<CecOperand> primaryMessages,
995             List<String> secondaryMessages,
996             long timeoutMillis)
997             throws CecClientWrapperException {
998         return checkMessagesInOrder(
999                 targetDevice, destination, primaryMessages, secondaryMessages, timeoutMillis);
1000     }
1001 
1002     /**
1003      * Checks that one of the message from the {@code primaryMessages} is sent from source device to
1004      * destination before sending any of the messages from the {@code secondaryMessages}
1005      * on the cec-client communication channel within give time.
1006      *
1007      * @param source            logical address of the source device.
1008      * @param destination       logical address of the destination device.
1009      * @param primaryMessages   list of CEC messages out of which at least one is expected from the
1010      *                          target device.
1011      * @param secondaryMessages list of CEC messages that are not expected before primary messages
1012      *                          to be sent from the target device.
1013      * @param timeoutMillis     timeout to monitor CEC messages from source device.
1014      * @return the first line that contains any of the primaryMessages.
1015      * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
1016      * are found, exception is thrown.
1017      */
checkMessagesInOrder( LogicalAddress source, LogicalAddress destination, List<CecOperand> primaryMessages, List<String> secondaryMessages, long timeoutMillis)1018     public String checkMessagesInOrder(
1019             LogicalAddress source,
1020             LogicalAddress destination,
1021             List<CecOperand> primaryMessages,
1022             List<String> secondaryMessages,
1023             long timeoutMillis)
1024             throws CecClientWrapperException {
1025         checkCecClient();
1026         long startTime = System.currentTimeMillis();
1027         long endTime = startTime;
1028         Pattern pattern = Pattern.compile("(.*>>)(.*?)"
1029                         + "(" + source + destination + "):"
1030                         + "(.*)",
1031                 Pattern.CASE_INSENSITIVE);
1032 
1033         while ((endTime - startTime <= timeoutMillis)) {
1034             try {
1035                 if (mInputConsole.ready()) {
1036                     String line = mInputConsole.readLine();
1037                     if (pattern.matcher(line).matches()) {
1038                         CecOperand operand = CecMessage.getOperand(line);
1039                         String params = CecMessage.getParamsAsString(line);
1040                         // Check for secondary messages. If found, throw an exception.
1041                         for (String secondaryMessage : secondaryMessages) {
1042                             if (line.contains(secondaryMessage)) {
1043                                 throw new CecClientWrapperException(ErrorCodes.CecMessageFound,
1044                                         operand.name() + " to " + destination + " with params "
1045                                                 + CecMessage.getParamsAsString(line));
1046                             }
1047                         }
1048                         // Check for the primary messages.
1049                         if (primaryMessages.contains(operand)) {
1050                             CLog.v("Found " + operand.name() + " in " + line);
1051                             return line;
1052                         }
1053                     }
1054                 }
1055             } catch (IOException ioe) {
1056                 throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
1057             }
1058             endTime = System.currentTimeMillis();
1059         }
1060         throw new CecClientWrapperException(
1061                 ErrorCodes.CecMessageNotFound, primaryMessages.toString());
1062     }
1063 
1064     /** Returns the device type that the cec-client has started as. */
getSelfDevice()1065     public LogicalAddress getSelfDevice() {
1066         return selfDevice;
1067     }
1068 
1069     /** Set the physical address of the cec-client instance */
setPhysicalAddress(int newPhysicalAddress)1070     public void setPhysicalAddress(int newPhysicalAddress) throws CecClientWrapperException {
1071         String command =
1072                 String.format(
1073                         "pa %02d %02d",
1074                         (newPhysicalAddress & 0xFF00) >> 8, newPhysicalAddress & 0xFF);
1075         sendConsoleMessage(command);
1076         physicalAddress = newPhysicalAddress;
1077     }
1078 
1079     /** Get the physical address of the cec-client instance, will return 0xFFFF if uninitialised */
getPhysicalAddress()1080     public int getPhysicalAddress() {
1081         return physicalAddress;
1082     }
1083 
clearClientOutput()1084     public void clearClientOutput() {
1085         mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream()));
1086     }
1087 
1088     /**
1089      * Kills the cec-client process that was created in init().
1090      */
killCecProcess()1091     private void killCecProcess() {
1092         try {
1093             boolean processQuit = false;
1094             checkCecClient();
1095             sendConsoleMessage(CecClientMessage.QUIT_CLIENT.toString());
1096             if (checkConsoleOutput(
1097                     CecClientMessage.CLIENT_CONSOLE_END.toString(), MILLISECONDS_TO_READY)) {
1098                 mOutputConsole.close();
1099                 mInputConsole.close();
1100                 if (mCecClient.waitFor(MILLISECONDS_TO_READY, TimeUnit.MILLISECONDS)) {
1101                     /* The cec-client process is quit */
1102                     processQuit = true;
1103                 }
1104             }
1105             mCecClientInitialised = false;
1106             if (!processQuit) {
1107                 /* Use a pkill cec-client if the cec-client process is not dead in spite of the
1108                  * quit above.
1109                  */
1110                 List<String> commands = new ArrayList<>();
1111                 Process killProcess;
1112                 commands.add("pkill");
1113                 commands.add("cec-client");
1114                 killProcess = RunUtil.getDefault().runCmdInBackground(commands);
1115                 killProcess.waitFor();
1116             }
1117         } catch (IOException | InterruptedException | CecClientWrapperException e) {
1118             /*
1119              * If cec-client is not running, do not throw a CecClientWrapperException, just return.
1120              */
1121             CLog.w(new CecClientWrapperException(ErrorCodes.CecClientStop, e));
1122         }
1123     }
1124 }
1125