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