1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hdmicec.cts.targetprep; 18 19 import android.hdmicec.cts.BaseHdmiCecCtsTest; 20 import android.hdmicec.cts.CecClientMessage; 21 import android.hdmicec.cts.CecMessage; 22 import android.hdmicec.cts.HdmiCecClientWrapper; 23 import android.hdmicec.cts.HdmiCecConstants; 24 import android.hdmicec.cts.LogicalAddress; 25 import android.hdmicec.cts.error.CecClientWrapperException; 26 import android.hdmicec.cts.error.ErrorCodes; 27 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.invoker.TestInformation; 31 import com.android.tradefed.log.LogUtil.CLog; 32 import com.android.tradefed.targetprep.BaseTargetPreparer; 33 import com.android.tradefed.targetprep.TargetSetupError; 34 import com.android.tradefed.util.RunUtil; 35 36 import java.io.BufferedReader; 37 import java.io.BufferedWriter; 38 import java.io.File; 39 import java.io.FileWriter; 40 import java.io.IOException; 41 import java.io.InputStreamReader; 42 import java.io.OutputStreamWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.concurrent.TimeUnit; 46 47 /* Sets up the CEC tests by discovering which port the CEC adapter connected to */ 48 public class CecPortDiscoverer extends BaseTargetPreparer { 49 50 private static final int TIMEOUT_MILLIS = 15000; 51 private static final int MAX_RETRY_COUNT = 3; 52 53 private File mCecMapDir = HdmiCecConstants.CEC_MAP_FOLDER; 54 private File mDeviceEntry = null; 55 private File mPortEntry = null; 56 57 private String instructionsOnError = 58 "\nIn case the setup is valid according to the README and " 59 + "the test should have run, please verify that\n" 60 + "1. cec-client is not running already on the port the DUT is connected to\n" 61 + "2. " 62 + HdmiCecConstants.CEC_MAP_FOLDER 63 + " has been cleared of stale mappings (in case a" 64 + " test was interrupted)\n"; 65 66 /** {@inheritDoc} */ 67 @Override setUp(TestInformation testInfo)68 public void setUp(TestInformation testInfo) 69 throws TargetSetupError, DeviceNotAvailableException { 70 ITestDevice device = testInfo.getDevice(); 71 if (!device.hasFeature("feature:android.hardware.hdmi.cec") 72 || !device.hasFeature("feature:android.software.leanback") 73 || isTvEmulator(device)) { 74 // We are testing non-HDMI devices, so don't check for adapter availability 75 return; 76 } 77 synchronized (CecPortDiscoverer.class) { 78 if (!mCecMapDir.exists()) { 79 mCecMapDir.mkdirs(); 80 } 81 initValidClient(device); 82 } 83 } 84 85 /** 86 * Check if the DUT is an emulator. 87 * @param device The DUT. 88 * @return true If the DUT is an emulator. 89 * @throws DeviceNotAvailableException 90 */ isTvEmulator(ITestDevice device)91 public static boolean isTvEmulator(ITestDevice device) throws DeviceNotAvailableException { 92 return !device.executeShellCommand("getprop " + HdmiCecConstants.PROPERTY_BUILD_FINGERPRINT 93 + " | grep \"cf_x86\"") 94 .isEmpty(); 95 } 96 97 /** {@inheritDoc} */ 98 @Override tearDown(TestInformation testInfo, Throwable e)99 public void tearDown(TestInformation testInfo, Throwable e) { 100 if (mDeviceEntry != null) { 101 mDeviceEntry.delete(); 102 } 103 if (mPortEntry != null) { 104 mPortEntry.delete(); 105 } 106 } 107 initValidClient(ITestDevice device)108 private void initValidClient(ITestDevice device) 109 throws TargetSetupError, DeviceNotAvailableException { 110 111 List<String> launchCommand = new ArrayList(); 112 Process mCecClient; 113 /* This is a semi-functional object only, the methods that we can use are limited. */ 114 HdmiCecClientWrapper cecClientWrapper = new HdmiCecClientWrapper(); 115 116 launchCommand.add("cec-client"); 117 String serialNo = ""; 118 119 try { 120 List<String> comPorts = cecClientWrapper.getValidCecClientPorts(); 121 122 if (comPorts.size() == 0) { 123 throw new TargetSetupError("No adapters connected to host."); 124 } 125 126 int targetDeviceType = 127 BaseHdmiCecCtsTest.getTargetLogicalAddress(device).getDeviceType(); 128 int toDevice; 129 launchCommand.add("-t"); 130 launchCommand.add("r"); 131 launchCommand.add("-t"); 132 if (targetDeviceType == HdmiCecConstants.CEC_DEVICE_TYPE_TV) { 133 toDevice = LogicalAddress.PLAYBACK_1.getLogicalAddressAsInt(); 134 launchCommand.add("p"); 135 } else { 136 toDevice = LogicalAddress.TV.getLogicalAddressAsInt(); 137 launchCommand.add("x"); 138 } 139 140 serialNo = device.getProperty("ro.serialno"); 141 String serialNoHashCode = String.valueOf(serialNo.hashCode()); 142 String serialNoParam = CecMessage.convertStringToHexParams(serialNoHashCode); 143 /* 144 * formatParams prefixes with a ':' that we do not want in the vendorcommand 145 * command line utility. 146 */ 147 serialNoParam = serialNoParam.substring(1); 148 StringBuilder sendVendorCommand = new StringBuilder("cmd hdmi_control vendorcommand "); 149 sendVendorCommand.append(" -t " + targetDeviceType); 150 sendVendorCommand.append(" -d " + toDevice); 151 sendVendorCommand.append(" -a " + serialNoParam); 152 153 for (String port : comPorts) { 154 launchCommand.add(port); 155 boolean portBeingRetried = true; 156 int retryCount = 0; 157 do { 158 File adapterMapping = new File(mCecMapDir, getPortFilename(port)); 159 /* 160 * Check for the mapping before each iteration. It is possible that another DUT 161 * got mapped to this port while this DUT is still trying to discover if this is 162 * the right port. 163 */ 164 if (adapterMapping.exists()) { 165 /* Exit the current port's retry loop */ 166 launchCommand.remove(port); 167 break; 168 } 169 mCecClient = RunUtil.getDefault().runCmdInBackground(launchCommand); 170 BufferedReader inputConsole = 171 new BufferedReader( 172 new InputStreamReader(mCecClient.getInputStream())); 173 BufferedWriter outputConsole = 174 new BufferedWriter( 175 new OutputStreamWriter(mCecClient.getOutputStream())); 176 try { 177 /* Wait for the client to become ready */ 178 if (cecClientWrapper.checkConsoleOutput( 179 CecClientMessage.CLIENT_CONSOLE_READY.toString(), 180 TIMEOUT_MILLIS, inputConsole)) { 181 182 device.executeShellCommand(sendVendorCommand.toString()); 183 if (cecClientWrapper.checkConsoleOutput( 184 serialNoParam, TIMEOUT_MILLIS, inputConsole)) { 185 if (targetDeviceType != HdmiCecConstants.CEC_DEVICE_TYPE_TV) { 186 // Timeout in milliseconds 187 long getVersionTimeout = 3000; 188 189 String getVersionMessage = "tx 10:9f"; 190 cecClientWrapper.sendConsoleMessage( 191 getVersionMessage, outputConsole); 192 String getVersionResponse = "01:9e"; 193 if (cecClientWrapper.checkConsoleOutput( 194 getVersionResponse, getVersionTimeout, inputConsole)) { 195 throw new Exception( 196 "Setup error! The sink device (TV) in the test setup" 197 + " seems to have CEC enabled. Please disable" 198 + " and retry tests."); 199 } 200 } 201 202 writeMapping(port, serialNo); 203 return; 204 } 205 /* Since it did not find the required message. Check another port */ 206 portBeingRetried = false; 207 } else { 208 CLog.e("Console did not get ready!"); 209 throw new CecClientWrapperException(ErrorCodes.CecPortBusy); 210 } 211 } catch (CecClientWrapperException cwe) { 212 if (cwe.getErrorCode() != ErrorCodes.CecPortBusy) { 213 retryCount = MAX_RETRY_COUNT; 214 } else { 215 retryCount++; 216 } 217 if (retryCount >= MAX_RETRY_COUNT) { 218 /* We have retried enough number of times. Check another port */ 219 portBeingRetried = false; 220 } else { 221 /* Give a break before checking the port again. */ 222 TimeUnit.MILLISECONDS.sleep(TIMEOUT_MILLIS); 223 } 224 } finally { 225 /* Kill the unwanted cec-client process. */ 226 boolean processQuit = false; 227 cecClientWrapper.sendConsoleMessage( 228 CecClientMessage.QUIT_CLIENT.toString(), outputConsole); 229 if (cecClientWrapper.checkConsoleOutput( 230 CecClientMessage.CLIENT_CONSOLE_END.toString(), 231 TIMEOUT_MILLIS, inputConsole)) { 232 outputConsole.close(); 233 inputConsole.close(); 234 if (mCecClient.waitFor(10, TimeUnit.SECONDS)) { 235 /* The cec-client process is quit */ 236 processQuit = true; 237 } 238 } 239 if (!processQuit) { 240 /* Use destryForcibly if the cec-client process is not dead 241 * in spite of the quit above. 242 */ 243 Process killProcess = mCecClient.destroyForcibly(); 244 killProcess.waitFor(60, TimeUnit.SECONDS); 245 } 246 } 247 } while (portBeingRetried); 248 launchCommand.remove(port); 249 } 250 } catch (IOException | InterruptedException e) { 251 throw new TargetSetupError( 252 "Caught " 253 + e.getClass().getSimpleName() 254 + ". " 255 + "Could not get adapter mapping for device" 256 + serialNo 257 + "." 258 + instructionsOnError, 259 e); 260 } catch (Exception generic) { 261 throw new TargetSetupError( 262 "Caught an exception with message '" 263 + generic.getMessage() 264 + "'. " 265 + "Could not get adapter mapping for device" 266 + serialNo 267 + "." 268 + instructionsOnError, 269 generic); 270 } 271 throw new TargetSetupError( 272 "Device " + serialNo + " not connected to any adapter!" + instructionsOnError); 273 } 274 getPortFilename(String port)275 private String getPortFilename(String port) { 276 /* Returns only the name of the port, ignoring the path */ 277 return new File(port).getName(); 278 } 279 writeMapping(String port, String serialNo)280 private void writeMapping(String port, String serialNo) throws TargetSetupError { 281 mDeviceEntry = new File(mCecMapDir, serialNo); 282 mPortEntry = new File(mCecMapDir, getPortFilename(port)); 283 try (BufferedWriter device = new BufferedWriter(new FileWriter(mDeviceEntry)); 284 BufferedWriter adapter = new BufferedWriter(new FileWriter(mPortEntry))) { 285 mDeviceEntry.createNewFile(); 286 device.write(port); 287 device.flush(); 288 adapter.write(serialNo); 289 adapter.flush(); 290 } catch (IOException ioe) { 291 throw new TargetSetupError( 292 "Could not create mapping file " + mCecMapDir + "/" + mDeviceEntry.getName()); 293 } 294 } 295 } 296