| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.hdmicec.cts; |
| |
| import android.hdmicec.cts.error.CecClientWrapperException; |
| import android.hdmicec.cts.error.ErrorCodes; |
| |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.util.RunUtil; |
| |
| import org.junit.rules.ExternalResource; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.OutputStreamWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import java.util.regex.Pattern; |
| |
| /** Class that helps communicate with the cec-client */ |
| public final class HdmiCecClientWrapper extends ExternalResource { |
| |
| private static final int MILLISECONDS_TO_READY = 10000; |
| private static final int DEFAULT_TIMEOUT = 20000; |
| private static final int BUFFER_SIZE = 1024; |
| |
| private Process mCecClient; |
| private BufferedWriter mOutputConsole; |
| private BufferedReader mInputConsole; |
| private boolean mCecClientInitialised = false; |
| |
| private LogicalAddress selfDevice = LogicalAddress.RECORDER_1; |
| private LogicalAddress targetDevice = LogicalAddress.UNKNOWN; |
| private String clientParams[]; |
| private StringBuilder sendVendorCommand = new StringBuilder("cmd hdmi_control vendorcommand "); |
| private int physicalAddress = 0xFFFF; |
| |
| private CecOperand featureAbortOperand = CecOperand.FEATURE_ABORT; |
| private List<Integer> featureAbortReasons = |
| new ArrayList<>(HdmiCecConstants.ABORT_INVALID_OPERAND); |
| private boolean isFeatureAbortExpected = false; |
| |
| private static final String CEC_PORT_BUSY = "unable to open the device on port"; |
| |
| public HdmiCecClientWrapper(String ...clientParams) { |
| this.clientParams = clientParams; |
| } |
| |
| @Override |
| protected void after() { |
| this.killCecProcess(); |
| } |
| |
| void setTargetLogicalAddress(LogicalAddress dutLogicalAddress) { |
| targetDevice = dutLogicalAddress; |
| } |
| |
| public List<String> getValidCecClientPorts() throws CecClientWrapperException { |
| |
| List<String> listPortsCommand = new ArrayList(); |
| Process cecClient; |
| |
| listPortsCommand.add("cec-client"); |
| listPortsCommand.add("-l"); |
| |
| List<String> comPorts = new ArrayList(); |
| try { |
| cecClient = RunUtil.getDefault().runCmdInBackground(listPortsCommand); |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException( |
| ErrorCodes.CecClientStart, |
| "as cec-client may not be installed. Please refer to README for" |
| + " setup/installation instructions."); |
| } |
| try { |
| BufferedReader inputConsole = |
| new BufferedReader(new InputStreamReader(cecClient.getInputStream())); |
| while (cecClient.isAlive()) { |
| if (inputConsole.ready()) { |
| String line = inputConsole.readLine(); |
| if (line.toLowerCase().contains("com port")) { |
| String port = line.split(":")[1].trim(); |
| comPorts.add(port); |
| } |
| } |
| } |
| inputConsole.close(); |
| cecClient.waitFor(); |
| } catch (IOException | InterruptedException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| |
| return comPorts; |
| } |
| |
| boolean initValidCecClient(ITestDevice device, List<String> clientCommands) |
| throws CecClientWrapperException { |
| |
| String serialNo; |
| List<String> launchCommand = new ArrayList(clientCommands); |
| try { |
| serialNo = device.getProperty("ro.serialno"); |
| } catch (DeviceNotAvailableException de) { |
| throw new CecClientWrapperException(ErrorCodes.DeviceNotAvailable, de); |
| } |
| File mDeviceEntry = new File(HdmiCecConstants.CEC_MAP_FOLDER, serialNo); |
| |
| try (BufferedReader reader = new BufferedReader(new FileReader(mDeviceEntry))) { |
| String port = reader.readLine(); |
| launchCommand.add(port); |
| mCecClient = RunUtil.getDefault().runCmdInBackground(launchCommand); |
| mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream())); |
| |
| /* Wait for the client to become ready */ |
| if (checkConsoleOutput( |
| CecClientMessage.CLIENT_CONSOLE_READY + "", MILLISECONDS_TO_READY)) { |
| mOutputConsole = |
| new BufferedWriter( |
| new OutputStreamWriter(mCecClient.getOutputStream()), |
| BUFFER_SIZE); |
| return true; |
| } else { |
| CLog.e("Console did not get ready!"); |
| /* Kill the unwanted cec-client process. */ |
| Process killProcess = mCecClient.destroyForcibly(); |
| killProcess.waitFor(); |
| } |
| } catch (IOException | InterruptedException ioe) { |
| throw new CecClientWrapperException( |
| ErrorCodes.ReadConsole, ioe, "Could not open port mapping file"); |
| } |
| return false; |
| } |
| |
| /** Initialise the client */ |
| void init(boolean startAsTv, ITestDevice device) throws CecClientWrapperException { |
| if (targetDevice == LogicalAddress.UNKNOWN) { |
| throw new CecClientWrapperException( |
| ErrorCodes.CecClientStart, "Missing logical address of the target device."); |
| } |
| |
| List<String> commands = new ArrayList(); |
| |
| commands.add("cec-client"); |
| |
| /* "-p 2" starts the client as if it is connected to HDMI port 2, taking the physical |
| * address 2.0.0.0 */ |
| commands.add("-p"); |
| commands.add("2"); |
| physicalAddress = 0x2000; |
| if (startAsTv) { |
| commands.add("-t"); |
| commands.add("x"); |
| selfDevice = LogicalAddress.TV; |
| } |
| /* "-d 15" set the log level to ERROR|WARNING|NOTICE|TRAFFIC */ |
| commands.add("-d"); |
| commands.add("15"); |
| commands.addAll(Arrays.asList(clientParams)); |
| if (Arrays.asList(clientParams).contains("a")) { |
| selfDevice = LogicalAddress.AUDIO_SYSTEM; |
| } |
| |
| mCecClientInitialised = true; |
| if (!initValidCecClient(device, commands)) { |
| mCecClientInitialised = false; |
| |
| throw new CecClientWrapperException(ErrorCodes.CecClientStart); |
| } |
| } |
| |
| private void checkCecClient() throws CecClientWrapperException { |
| if (!mCecClientInitialised) { |
| throw new CecClientWrapperException(ErrorCodes.CecClientStart); |
| } |
| if (!mCecClient.isAlive()) { |
| throw new CecClientWrapperException(ErrorCodes.CecClientNotRunning); |
| } |
| } |
| |
| /** |
| * Sends a CEC message with source marked as broadcast to the device passed in the constructor |
| * through the output console of the cec-communication channel. |
| */ |
| public void sendCecMessage(CecOperand message) throws CecClientWrapperException { |
| sendCecMessage(message, ""); |
| } |
| |
| /** |
| * Sends a CEC message with source marked as broadcast to the device passed in the constructor |
| * through the output console of the cec-communication channel. |
| */ |
| public void sendCecMessage(CecOperand message, String params) throws CecClientWrapperException { |
| sendCecMessage(LogicalAddress.BROADCAST, targetDevice, message, params); |
| } |
| |
| /** |
| * Sends a CEC message from source device to the device passed in the constructor through the |
| * output console of the cec-communication channel. |
| */ |
| public void sendCecMessage(LogicalAddress source, CecOperand message) |
| throws CecClientWrapperException { |
| sendCecMessage(source, targetDevice, message, ""); |
| } |
| |
| /** |
| * Sends a CEC message from source device to the device passed in the constructor through the |
| * output console of the cec-communication channel with the appended params. |
| */ |
| public void sendCecMessage(LogicalAddress source, CecOperand message, String params) |
| throws Exception { |
| sendCecMessage(source, targetDevice, message, params); |
| } |
| |
| /** |
| * Sends a CEC message from source device to a destination device through the output console of |
| * the cec-communication channel. |
| */ |
| public void sendCecMessage( |
| LogicalAddress source, LogicalAddress destination, CecOperand message) |
| throws CecClientWrapperException { |
| sendCecMessage(source, destination, message, ""); |
| } |
| |
| /** |
| * Broadcasts a CEC ACTIVE_SOURCE message from client device source through the output console |
| * of the cec-communication channel. |
| */ |
| public void broadcastActiveSource(LogicalAddress source) throws CecClientWrapperException { |
| int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF; |
| sendCecMessage( |
| source, |
| LogicalAddress.BROADCAST, |
| CecOperand.ACTIVE_SOURCE, |
| CecMessage.formatParams(sourcePa, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH)); |
| } |
| |
| /** |
| * Broadcasts a CEC ACTIVE_SOURCE message with physicalAddressOfActiveDevice from client device |
| * source through the output console of the cec-communication channel. |
| */ |
| public void broadcastActiveSource(LogicalAddress source, int physicalAddressOfActiveDevice) |
| throws CecClientWrapperException { |
| sendCecMessage( |
| source, |
| LogicalAddress.BROADCAST, |
| CecOperand.ACTIVE_SOURCE, |
| CecMessage.formatParams( |
| physicalAddressOfActiveDevice, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH)); |
| } |
| |
| /** |
| * Broadcasts a CEC REPORT_PHYSICAL_ADDRESS message from client device source through the output |
| * console of the cec-communication channel. |
| */ |
| public void broadcastReportPhysicalAddress(LogicalAddress source) |
| throws CecClientWrapperException { |
| String deviceType = CecMessage.formatParams(source.getDeviceType()); |
| int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF; |
| String physicalAddress = |
| CecMessage.formatParams(sourcePa, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH); |
| sendCecMessage( |
| source, |
| LogicalAddress.BROADCAST, |
| CecOperand.REPORT_PHYSICAL_ADDRESS, |
| physicalAddress + deviceType); |
| } |
| |
| /** |
| * Broadcasts a CEC REPORT_PHYSICAL_ADDRESS message with physicalAddressToReport from client |
| * device source through the output console of the cec-communication channel. |
| */ |
| public void broadcastReportPhysicalAddress(LogicalAddress source, int physicalAddressToReport) |
| throws CecClientWrapperException { |
| String deviceType = CecMessage.formatParams(source.getDeviceType()); |
| String physicalAddress = |
| CecMessage.formatParams( |
| physicalAddressToReport, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH); |
| sendCecMessage( |
| source, |
| LogicalAddress.BROADCAST, |
| CecOperand.REPORT_PHYSICAL_ADDRESS, |
| physicalAddress + deviceType); |
| } |
| |
| /** |
| * Sends a CEC message from source device to a destination device through the output console of |
| * the cec-communication channel with the appended params. |
| */ |
| public void sendCecMessage( |
| LogicalAddress source, LogicalAddress destination, CecOperand message, String params) |
| throws CecClientWrapperException { |
| checkCecClient(); |
| String sendMessageString = "tx " + source + destination + ":" + message + params; |
| try { |
| CLog.v("Sending CEC message: " + sendMessageString); |
| mOutputConsole.write(sendMessageString); |
| mOutputConsole.newLine(); |
| mOutputConsole.flush(); |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe); |
| } |
| } |
| |
| public void sendMultipleUserControlPressAndRelease( |
| LogicalAddress source, List<Integer> keycodes) throws CecClientWrapperException { |
| try { |
| for (int keycode : keycodes) { |
| String key = String.format("%02x", keycode); |
| mOutputConsole.write( |
| "tx " |
| + source |
| + targetDevice |
| + ":" |
| + CecOperand.USER_CONTROL_PRESSED |
| + ":" |
| + key); |
| mOutputConsole.newLine(); |
| mOutputConsole.write( |
| "tx " + source + targetDevice + ":" + CecOperand.USER_CONTROL_RELEASED); |
| mOutputConsole.newLine(); |
| mOutputConsole.flush(); |
| TimeUnit.MILLISECONDS.sleep(200); |
| } |
| } catch (InterruptedException | IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe); |
| } |
| } |
| |
| /** |
| * Sends a <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> from source to device through the |
| * output console of the cec-communication channel with the mentioned keycode. |
| */ |
| public void sendUserControlPressAndRelease(LogicalAddress source, int keycode, boolean holdKey) |
| throws CecClientWrapperException { |
| sendUserControlPressAndRelease(source, targetDevice, keycode, holdKey); |
| } |
| |
| /** |
| * Sends a <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> from source to destination |
| * through the output console of the cec-communication channel with the mentioned keycode. |
| */ |
| public void sendUserControlPressAndRelease( |
| LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey) |
| throws CecClientWrapperException { |
| sendUserControlPress(source, destination, keycode, holdKey); |
| try { |
| /* Sleep less than 200ms between press and release */ |
| TimeUnit.MILLISECONDS.sleep(100); |
| mOutputConsole.write( |
| "tx " + source + destination + ":" + CecOperand.USER_CONTROL_RELEASED); |
| mOutputConsole.flush(); |
| } catch (IOException | InterruptedException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe); |
| } |
| } |
| |
| /** |
| * Sends a {@code <UCP>} with and additional param. This is used to check that the DUT ignores |
| * additional params in an otherwise correct message. |
| */ |
| public void sendUserControlPressAndReleaseWithAdditionalParams( |
| LogicalAddress source, LogicalAddress destination, int keyCode, int additionalParam) |
| throws CecClientWrapperException { |
| String key = String.format("%02x", keyCode); |
| String command = |
| "tx " |
| + source |
| + destination |
| + ":" |
| + CecOperand.USER_CONTROL_PRESSED |
| + ":" |
| + key |
| + ":" |
| + additionalParam; |
| |
| try { |
| mOutputConsole.write(command); |
| mOutputConsole.newLine(); |
| mOutputConsole.write( |
| "tx " + source + destination + ":" + CecOperand.USER_CONTROL_RELEASED); |
| mOutputConsole.newLine(); |
| mOutputConsole.flush(); |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe); |
| } |
| } |
| |
| /** |
| * Sends a <UCP> message from source to destination through the output console of the |
| * cec-communication channel with the mentioned keycode. If holdKey is true, the method will |
| * send multiple <UCP> messages to simulate a long press. No <UCR> will be sent. |
| */ |
| public void sendUserControlPress( |
| LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey) |
| throws CecClientWrapperException { |
| String key = String.format("%02x", keycode); |
| String command = "tx " + source + destination + ":" + |
| CecOperand.USER_CONTROL_PRESSED + ":" + key; |
| |
| try { |
| if (holdKey) { |
| /* Repeat once every 450ms for at least 5 seconds. Send 11 times in loop every |
| * 450ms. The message is sent once after the loop as well. |
| * ((11 + 1) * 0.45 = 5.4s total) */ |
| int repeat = 11; |
| for (int i = 0; i < repeat; i++) { |
| mOutputConsole.write(command); |
| mOutputConsole.newLine(); |
| mOutputConsole.flush(); |
| TimeUnit.MILLISECONDS.sleep(450); |
| } |
| } |
| |
| mOutputConsole.write(command); |
| mOutputConsole.newLine(); |
| mOutputConsole.flush(); |
| } catch (IOException | InterruptedException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe); |
| } |
| } |
| |
| /** |
| * Sends a series of <UCP> [firstKeycode] from source to destination through the output console |
| * of the cec-communication channel immediately followed by <UCP> [secondKeycode]. No <UCR> |
| * message is sent. |
| */ |
| public void sendUserControlInterruptedPressAndHold( |
| LogicalAddress source, |
| LogicalAddress destination, |
| int firstKeycode, |
| int secondKeycode, |
| boolean holdKey) |
| throws CecClientWrapperException { |
| sendUserControlPress(source, destination, firstKeycode, holdKey); |
| try { |
| /* Sleep less than 200ms between press and release */ |
| TimeUnit.MILLISECONDS.sleep(100); |
| } catch (InterruptedException ie) { |
| throw new CecClientWrapperException(ErrorCodes.WriteConsole, ie); |
| } |
| sendUserControlPress(source, destination, secondKeycode, false); |
| } |
| |
| /** Sends a poll message to the device */ |
| public void sendPoll() throws Exception { |
| sendPoll(targetDevice); |
| } |
| |
| /** Sends a poll message to the destination */ |
| public void sendPoll(LogicalAddress destination) throws Exception { |
| String command = CecClientMessage.POLL + " " + destination; |
| sendConsoleMessage(command); |
| } |
| |
| |
| /** Sends a message to the output console of the cec-client */ |
| public void sendConsoleMessage(String message) throws CecClientWrapperException { |
| sendConsoleMessage(message, mOutputConsole); |
| } |
| |
| /** Sends a message to the output console of the cec-client */ |
| public void sendConsoleMessage(String message, BufferedWriter outputConsole) |
| throws CecClientWrapperException { |
| CLog.v("Sending console message:: " + message); |
| try { |
| outputConsole.write(message); |
| outputConsole.flush(); |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe); |
| } |
| } |
| |
| /** Check for any string on the input console of the cec-client, uses default timeout */ |
| public boolean checkConsoleOutput(String expectedMessage) throws CecClientWrapperException { |
| return checkConsoleOutput(expectedMessage, DEFAULT_TIMEOUT); |
| } |
| |
| /** Check for any string on the input console of the cec-client */ |
| public boolean checkConsoleOutput(String expectedMessage, long timeoutMillis) |
| throws CecClientWrapperException { |
| checkCecClient(); |
| return checkConsoleOutput(expectedMessage, timeoutMillis, mInputConsole); |
| } |
| |
| /** Check for any string on the specified input console */ |
| public boolean checkConsoleOutput( |
| String expectedMessage, long timeoutMillis, BufferedReader inputConsole) |
| throws CecClientWrapperException { |
| long startTime = System.currentTimeMillis(); |
| long endTime = startTime; |
| |
| while ((endTime - startTime <= timeoutMillis)) { |
| try { |
| if (inputConsole.ready()) { |
| String line = inputConsole.readLine(); |
| if (line != null |
| && line.toLowerCase().contains(expectedMessage.toLowerCase())) { |
| CLog.v("Found " + expectedMessage + " in " + line); |
| return true; |
| } else if (line.toLowerCase().contains(CEC_PORT_BUSY.toLowerCase())) { |
| throw new CecClientWrapperException(ErrorCodes.CecPortBusy); |
| } |
| } |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| endTime = System.currentTimeMillis(); |
| } |
| return false; |
| } |
| |
| /** Gets all the messages received from the given list of source devices during a period of |
| * duration seconds. |
| */ |
| public List<CecOperand> getAllMessages(List<LogicalAddress> sourceList, int duration) |
| throws CecClientWrapperException { |
| List<CecOperand> receivedOperands = new ArrayList<>(); |
| long startTime = System.currentTimeMillis(); |
| long endTime = startTime; |
| |
| String source = sourceList.toString().replace(",", "").replace(" ", ""); |
| |
| Pattern pattern = Pattern.compile("(.*>>)(.*?)" + |
| "(" + source + "\\p{XDigit}):(.*)", |
| Pattern.CASE_INSENSITIVE); |
| |
| while ((endTime - startTime <= (duration * 1000))) { |
| try { |
| if (mInputConsole.ready()) { |
| String line = mInputConsole.readLine(); |
| if (pattern.matcher(line).matches()) { |
| CecOperand operand = CecMessage.getOperand(line); |
| if (!receivedOperands.contains(operand)) { |
| receivedOperands.add(operand); |
| } |
| } |
| } |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| endTime = System.currentTimeMillis(); |
| } |
| return receivedOperands; |
| } |
| |
| /** |
| * Gets the list of logical addresses which receives messages with operand expectedMessage |
| * during a period of duration seconds. |
| */ |
| public List<LogicalAddress> getAllDestLogicalAddresses(CecOperand expectedMessage, int duration) |
| throws CecClientWrapperException { |
| return getAllDestLogicalAddresses(expectedMessage, "", duration); |
| } |
| |
| /** |
| * Gets the list of logical addresses which receives messages with operand expectedMessage and |
| * params during a period of duration seconds. |
| */ |
| public List<LogicalAddress> getAllDestLogicalAddresses( |
| CecOperand expectedMessage, String params, int duration) |
| throws CecClientWrapperException { |
| List<LogicalAddress> destinationAddresses = new ArrayList<>(); |
| long startTime = System.currentTimeMillis(); |
| long endTime = startTime; |
| Pattern pattern = |
| Pattern.compile( |
| "(.*>>)(.*?)" + ":(" + expectedMessage + params + ")(.*)", |
| Pattern.CASE_INSENSITIVE); |
| |
| while ((endTime - startTime <= (duration * 1000))) { |
| try { |
| if (mInputConsole.ready()) { |
| String line = mInputConsole.readLine(); |
| if (pattern.matcher(line).matches()) { |
| LogicalAddress destination = CecMessage.getDestination(line); |
| if (!destinationAddresses.contains(destination)) { |
| destinationAddresses.add(destination); |
| } |
| } |
| } |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| endTime = System.currentTimeMillis(); |
| } |
| return destinationAddresses; |
| } |
| |
| /** |
| * The next checkExpectedOutput calls will also permit a feature abort as an alternate to the |
| * expected operand. The feature abort will be permissible if it has |
| * |
| * @param abortForOperand The operand for which the feature abort could be an allowed response |
| * @param reasons List of allowed reasons that the feature abort message could have |
| */ |
| private void setExpectFeatureAbortFor(CecOperand abortOperand, Integer... abortReasons) { |
| isFeatureAbortExpected = true; |
| featureAbortOperand = abortOperand; |
| featureAbortReasons = Arrays.asList(abortReasons); |
| } |
| |
| /** Removes feature abort as a permissible alternate response for {@link checkExpectedOutput} */ |
| private void unsetExpectFeatureAbort() { |
| isFeatureAbortExpected = false; |
| CecOperand featureAbortOperand = CecOperand.FEATURE_ABORT; |
| List<Integer> featureAbortReasons = new ArrayList<>(HdmiCecConstants.ABORT_INVALID_OPERAND); |
| } |
| |
| /** |
| * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and |
| * returns the first line that contains that message within default timeout. If the CEC message |
| * is not found within the timeout, an CecClientWrapperException is thrown. |
| */ |
| public String checkExpectedOutput(CecOperand expectedMessage) throws CecClientWrapperException { |
| return checkExpectedOutput( |
| targetDevice, LogicalAddress.BROADCAST, expectedMessage, DEFAULT_TIMEOUT, false); |
| } |
| |
| /** |
| * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client communication |
| * channel and returns the first line that contains that message within default timeout. If the |
| * CEC message is not found within the timeout, an CecClientWrapperException is thrown. |
| */ |
| public String checkExpectedOutput(LogicalAddress toDevice, CecOperand expectedMessage) |
| throws CecClientWrapperException { |
| return checkExpectedOutput(targetDevice, toDevice, expectedMessage, DEFAULT_TIMEOUT, false); |
| } |
| |
| /** |
| * Looks for the broadcasted CEC expectedMessage sent from cec-client device fromDevice on the |
| * cec-client communication channel and returns the first line that contains that message within |
| * default timeout. If the CEC message is not found within the timeout, an |
| * CecClientWrapperException is thrown. |
| */ |
| public String checkExpectedMessageFromClient( |
| LogicalAddress fromDevice, CecOperand expectedMessage) |
| throws CecClientWrapperException { |
| return checkExpectedMessageFromClient( |
| fromDevice, LogicalAddress.BROADCAST, expectedMessage); |
| } |
| |
| /** |
| * Looks for the CEC expectedMessage sent from cec-client device fromDevice to CEC device |
| * toDevice on the cec-client communication channel and returns the first line that contains |
| * that message within default timeout. If the CEC message is not found within the timeout, an |
| * CecClientWrapperException is thrown. |
| */ |
| public String checkExpectedMessageFromClient( |
| LogicalAddress fromDevice, LogicalAddress toDevice, CecOperand expectedMessage) |
| throws CecClientWrapperException { |
| return checkExpectedOutput(fromDevice, toDevice, expectedMessage, DEFAULT_TIMEOUT, true); |
| } |
| |
| /** |
| * Looks for the CEC expectedMessage or a {@code <Feature Abort>} for {@code |
| * featureAbortOperand} with one of the abort reasons in {@code abortReason} is sent from |
| * cec-client device fromDevice to the DUT on the cec-client communication channel and returns |
| * the first line that contains that message within default timeout. If the CEC message is not |
| * found within the timeout, a CecClientWrapperException is thrown. |
| */ |
| public String checkExpectedOutputOrFeatureAbort( |
| LogicalAddress fromDevice, |
| CecOperand expectedMessage, |
| CecOperand featureAbortOperand, |
| Integer... featureAbortReasons) |
| throws CecClientWrapperException { |
| setExpectFeatureAbortFor(featureAbortOperand, featureAbortReasons); |
| String message = |
| checkExpectedOutput( |
| targetDevice, fromDevice, expectedMessage, DEFAULT_TIMEOUT, false); |
| unsetExpectFeatureAbort(); |
| return message; |
| } |
| |
| /** |
| * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and |
| * returns the first line that contains that message within timeoutMillis. If the CEC message is |
| * not found within the timeout, an CecClientWrapperException is thrown. |
| */ |
| public String checkExpectedOutput(CecOperand expectedMessage, long timeoutMillis) |
| throws CecClientWrapperException { |
| return checkExpectedOutput( |
| targetDevice, LogicalAddress.BROADCAST, expectedMessage, timeoutMillis, false); |
| } |
| |
| /** |
| * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client communication |
| * channel and returns the first line that contains that message within timeoutMillis. If the |
| * CEC message is not found within the timeout, an CecClientWrapperException is thrown. |
| */ |
| public String checkExpectedOutput( |
| LogicalAddress toDevice, CecOperand expectedMessage, long timeoutMillis) |
| throws CecClientWrapperException { |
| return checkExpectedOutput(targetDevice, toDevice, expectedMessage, timeoutMillis, false); |
| } |
| |
| /** |
| * Looks for the CEC expectedMessage sent from CEC device fromDevice to CEC device toDevice on |
| * the cec-client communication channel and returns the first line that contains that message |
| * within timeoutMillis. If the CEC message is not found within the timeout, an |
| * CecClientWrapperException is thrown. This method looks for the CEC messages coming from |
| * Cec-client if fromCecClient is true. |
| */ |
| public String checkExpectedOutput( |
| LogicalAddress fromDevice, |
| LogicalAddress toDevice, |
| CecOperand expectedMessage, |
| long timeoutMillis, |
| boolean fromCecClient) |
| throws CecClientWrapperException { |
| checkCecClient(); |
| long startTime = System.currentTimeMillis(); |
| long endTime = startTime; |
| String direction = fromCecClient ? "<<" : ">>"; |
| Pattern pattern; |
| if (expectedMessage == CecOperand.POLL) { |
| pattern = |
| Pattern.compile( |
| "(.*" |
| + direction |
| + ")(.*?)" |
| + "(" |
| + fromDevice |
| + toDevice |
| + ")(.*)", |
| Pattern.CASE_INSENSITIVE); |
| } else { |
| String expectedOperands = expectedMessage.toString(); |
| if (isFeatureAbortExpected) { |
| expectedOperands += "|" + CecOperand.FEATURE_ABORT; |
| } |
| pattern = |
| Pattern.compile( |
| "(.*" |
| + direction |
| + ")(.*?)" |
| + "(" |
| + fromDevice |
| + toDevice |
| + "):" |
| + "(" |
| + expectedOperands |
| + ")(.*)", |
| Pattern.CASE_INSENSITIVE); |
| } |
| while ((endTime - startTime <= timeoutMillis)) { |
| try { |
| if (mInputConsole.ready()) { |
| String line = mInputConsole.readLine(); |
| if (pattern.matcher(line).matches()) { |
| if (isFeatureAbortExpected |
| && CecMessage.getOperand(line) == CecOperand.FEATURE_ABORT) { |
| CecOperand featureAbortedFor = |
| CecOperand.getOperand(CecMessage.getParams(line, 0, 2)); |
| int reason = CecMessage.getParams(line, 2, 4); |
| if (featureAbortedFor == featureAbortOperand |
| && featureAbortReasons.contains(reason)) { |
| return line; |
| } else { |
| continue; |
| } |
| } |
| CLog.v("Found " + expectedMessage.name() + " in " + line); |
| return line; |
| } |
| } |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| endTime = System.currentTimeMillis(); |
| } |
| throw new CecClientWrapperException(ErrorCodes.CecMessageNotFound, expectedMessage.name()); |
| } |
| |
| public void checkNoMessagesSentFromDevice(int timeoutMillis, List<CecOperand> excludeOperands) |
| throws CecClientWrapperException { |
| checkCecClient(); |
| long startTime = System.currentTimeMillis(); |
| long endTime = startTime; |
| Pattern pattern = |
| Pattern.compile("(.*>>)(.*?)(" |
| + targetDevice |
| + "\\p{XDigit}):(.*)", |
| Pattern.CASE_INSENSITIVE); |
| while ((endTime - startTime <= timeoutMillis)) { |
| try { |
| if (mInputConsole.ready()) { |
| String line = mInputConsole.readLine(); |
| if (pattern.matcher(line).matches()) { |
| CecOperand operand = CecMessage.getOperand(line); |
| if(excludeOperands.contains(operand)){ |
| continue; |
| } |
| CLog.v("Found unexpected message in " + line); |
| throw new CecClientWrapperException( |
| ErrorCodes.CecMessageFound, |
| CecMessage.getOperand(line) |
| + " from " |
| + targetDevice |
| + " with params " |
| + CecMessage.getParamsAsString(line)); |
| } |
| } |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| endTime = System.currentTimeMillis(); |
| } |
| } |
| |
| public void checkNoMessagesSentFromDevice(int timeoutMillis) |
| throws CecClientWrapperException { |
| List<CecOperand> excludeOperands = new ArrayList<>(); |
| checkNoMessagesSentFromDevice(timeoutMillis, excludeOperands); |
| } |
| |
| /** |
| * Looks for the CEC message incorrectMessage sent to CEC device toDevice on the cec-client |
| * communication channel and throws an CecClientWrapperException if it finds the line that |
| * contains the message within the default timeout. If the CEC message is not found within the |
| * timeout, function returns without error. |
| */ |
| public void checkOutputDoesNotContainMessage( |
| LogicalAddress toDevice, CecOperand incorrectMessage) throws CecClientWrapperException { |
| checkOutputDoesNotContainMessage(toDevice, incorrectMessage, "", DEFAULT_TIMEOUT); |
| } |
| |
| /** |
| * Looks for the CEC message incorrectMessage along with the params sent to CEC device toDevice |
| * on the cec-client communication channel and throws an CecClientWrapperException if it finds |
| * the line that contains the message with its params within the default timeout. If the CEC |
| * message is not found within the timeout, function returns without error. |
| */ |
| public void checkOutputDoesNotContainMessage( |
| LogicalAddress toDevice, CecOperand incorrectMessage, String params) |
| throws CecClientWrapperException { |
| checkOutputDoesNotContainMessage(toDevice, incorrectMessage, params, DEFAULT_TIMEOUT); |
| } |
| |
| /** |
| * Looks for the CEC message incorrectMessage sent to CEC device toDevice on the cec-client |
| * communication channel and throws an CecClientWrapperException if it finds the line that |
| * contains the message within timeoutMillis. If the CEC message is not found within the |
| * timeout, function returns without error. |
| */ |
| public void checkOutputDoesNotContainMessage( |
| LogicalAddress toDevice, CecOperand incorrectMessage, long timeoutMillis) |
| throws CecClientWrapperException { |
| checkOutputDoesNotContainMessage(toDevice, incorrectMessage, "", timeoutMillis); |
| } |
| |
| /** |
| * Looks for the CEC message incorrectMessage along with the params sent to CEC device toDevice |
| * on the cec-client communication channel and throws an CecClientWrapperException if it finds |
| * the line that contains the message and params within timeoutMillis. If the CEC message is not |
| * found within the timeout, function returns without error. |
| */ |
| public void checkOutputDoesNotContainMessage( |
| LogicalAddress toDevice, CecOperand incorrectMessage, String params, long timeoutMillis) |
| throws CecClientWrapperException { |
| checkCecClient(); |
| long startTime = System.currentTimeMillis(); |
| long endTime = startTime; |
| Pattern pattern = |
| Pattern.compile( |
| "(.*>>)(.*?)" |
| + "(" |
| + targetDevice |
| + toDevice |
| + "):" |
| + "(" |
| + incorrectMessage |
| + params |
| + ")(.*)", |
| Pattern.CASE_INSENSITIVE); |
| |
| while ((endTime - startTime <= timeoutMillis)) { |
| try { |
| if (mInputConsole.ready()) { |
| String line = mInputConsole.readLine(); |
| if (pattern.matcher(line).matches()) { |
| CLog.v("Found " + incorrectMessage.name() + " in " + line); |
| throw new CecClientWrapperException( |
| ErrorCodes.CecMessageFound, |
| incorrectMessage.name() |
| + " to " |
| + toDevice |
| + " with params " |
| + CecMessage.getParamsAsString(line)); |
| } |
| } |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| endTime = System.currentTimeMillis(); |
| } |
| } |
| |
| /** |
| * Checks that one of the message from the {@code primaryMessages} is broadcasted from target |
| * device before sending any of the messages from the {@code secondaryMessages} on the |
| * cec-client communication channel within default time. |
| * |
| * @param primaryMessages list of CEC messages out of which at least one is expected from the |
| * target device. |
| * @param secondaryMessages list of CEC messages that are not expected before primary messages |
| * to be sent from the target device. |
| * @return the first line that contains any of the primaryMessages. |
| * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages} |
| * are found, exception is thrown. |
| */ |
| public String checkMessagesInOrder( |
| List<CecOperand> primaryMessages, |
| List<String> secondaryMessages) |
| throws CecClientWrapperException { |
| return checkMessagesInOrder(LogicalAddress.BROADCAST, primaryMessages, secondaryMessages); |
| } |
| |
| /** |
| * Checks that one of the message from the {@code primaryMessages} is sent from target |
| * device to destination before sending any of the messages from the {@code secondaryMessages} |
| * on the cec-client communication channel within default time. |
| * |
| * @param destination logical address of the destination device. |
| * @param primaryMessages list of CEC messages out of which at least one is expected from the |
| * target device. |
| * @param secondaryMessages list of CEC messages that are not expected before primary messages |
| * to be sent from the target device. |
| * @return the first line that contains any of the primaryMessages. |
| * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages} |
| * are found, exception is thrown. |
| */ |
| public String checkMessagesInOrder( |
| LogicalAddress destination, |
| List<CecOperand> primaryMessages, |
| List<String> secondaryMessages) |
| throws CecClientWrapperException { |
| return checkMessagesInOrder( |
| destination, primaryMessages, secondaryMessages, DEFAULT_TIMEOUT); |
| } |
| |
| /** |
| * Checks that one of the message from the {@code primaryMessages} is sent from target |
| * device to destination before sending any of the messages from the {@code secondaryMessages} |
| * on the cec-client communication channel within give time. |
| * |
| * @param destination logical address of the destination device. |
| * @param primaryMessages list of CEC messages out of which at least one is expected from the |
| * target device. |
| * @param secondaryMessages list of CEC messages that are not expected before primary messages |
| * to be sent from the target device. |
| * @param timeoutMillis timeout to monitor CEC messages from source device. |
| * @return the first line that contains any of the primaryMessages. |
| * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages} |
| * are found, exception is thrown. |
| */ |
| public String checkMessagesInOrder( |
| LogicalAddress destination, |
| List<CecOperand> primaryMessages, |
| List<String> secondaryMessages, |
| long timeoutMillis) |
| throws CecClientWrapperException { |
| return checkMessagesInOrder( |
| targetDevice, destination, primaryMessages, secondaryMessages, timeoutMillis); |
| } |
| |
| /** |
| * Checks that one of the message from the {@code primaryMessages} is sent from source device to |
| * destination before sending any of the messages from the {@code secondaryMessages} |
| * on the cec-client communication channel within give time. |
| * |
| * @param source logical address of the source device. |
| * @param destination logical address of the destination device. |
| * @param primaryMessages list of CEC messages out of which at least one is expected from the |
| * target device. |
| * @param secondaryMessages list of CEC messages that are not expected before primary messages |
| * to be sent from the target device. |
| * @param timeoutMillis timeout to monitor CEC messages from source device. |
| * @return the first line that contains any of the primaryMessages. |
| * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages} |
| * are found, exception is thrown. |
| */ |
| public String checkMessagesInOrder( |
| LogicalAddress source, |
| LogicalAddress destination, |
| List<CecOperand> primaryMessages, |
| List<String> secondaryMessages, |
| long timeoutMillis) |
| throws CecClientWrapperException { |
| checkCecClient(); |
| long startTime = System.currentTimeMillis(); |
| long endTime = startTime; |
| Pattern pattern = Pattern.compile("(.*>>)(.*?)" |
| + "(" + source + destination + "):" |
| + "(.*)", |
| Pattern.CASE_INSENSITIVE); |
| |
| while ((endTime - startTime <= timeoutMillis)) { |
| try { |
| if (mInputConsole.ready()) { |
| String line = mInputConsole.readLine(); |
| if (pattern.matcher(line).matches()) { |
| CecOperand operand = CecMessage.getOperand(line); |
| String params = CecMessage.getParamsAsString(line); |
| // Check for secondary messages. If found, throw an exception. |
| for (String secondaryMessage : secondaryMessages) { |
| if (line.contains(secondaryMessage)) { |
| throw new CecClientWrapperException(ErrorCodes.CecMessageFound, |
| operand.name() + " to " + destination + " with params " |
| + CecMessage.getParamsAsString(line)); |
| } |
| } |
| // Check for the primary messages. |
| if (primaryMessages.contains(operand)) { |
| CLog.v("Found " + operand.name() + " in " + line); |
| return line; |
| } |
| } |
| } |
| } catch (IOException ioe) { |
| throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe); |
| } |
| endTime = System.currentTimeMillis(); |
| } |
| throw new CecClientWrapperException( |
| ErrorCodes.CecMessageNotFound, primaryMessages.toString()); |
| } |
| |
| /** Returns the device type that the cec-client has started as. */ |
| public LogicalAddress getSelfDevice() { |
| return selfDevice; |
| } |
| |
| /** Set the physical address of the cec-client instance */ |
| public void setPhysicalAddress(int newPhysicalAddress) throws CecClientWrapperException { |
| String command = |
| String.format( |
| "pa %02d %02d", |
| (newPhysicalAddress & 0xFF00) >> 8, newPhysicalAddress & 0xFF); |
| sendConsoleMessage(command); |
| physicalAddress = newPhysicalAddress; |
| } |
| |
| /** Get the physical address of the cec-client instance, will return 0xFFFF if uninitialised */ |
| public int getPhysicalAddress() { |
| return physicalAddress; |
| } |
| |
| public void clearClientOutput() { |
| mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream())); |
| } |
| |
| /** |
| * Kills the cec-client process that was created in init(). |
| */ |
| private void killCecProcess() { |
| try { |
| checkCecClient(); |
| sendConsoleMessage(CecClientMessage.QUIT_CLIENT.toString()); |
| mOutputConsole.close(); |
| mInputConsole.close(); |
| mCecClientInitialised = false; |
| if (!mCecClient.waitFor(MILLISECONDS_TO_READY, TimeUnit.MILLISECONDS)) { |
| /* Use a pkill cec-client if the cec-client process is not dead in spite of the |
| * quit above. |
| */ |
| List<String> commands = new ArrayList<>(); |
| Process killProcess; |
| commands.add("pkill"); |
| commands.add("cec-client"); |
| killProcess = RunUtil.getDefault().runCmdInBackground(commands); |
| killProcess.waitFor(); |
| } |
| } catch (IOException | InterruptedException | CecClientWrapperException e) { |
| /* |
| * If cec-client is not running, do not throw a CecClientWrapperException, just return. |
| */ |
| CLog.w(new CecClientWrapperException(ErrorCodes.CecClientStop, e)); |
| } |
| } |
| } |