1 /* 2 * Copyright (C) 2014 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 com.android.tradefed.util; 18 19 import com.android.tradefed.device.CollectingOutputReceiver; 20 import com.android.tradefed.device.DeviceNotAvailableException; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.result.FileInputStreamSource; 24 import com.android.tradefed.result.ITestInvocationListener; 25 import com.android.tradefed.result.InputStreamSource; 26 import com.android.tradefed.result.LogDataType; 27 import com.android.tradefed.util.sl4a.Sl4aClient; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.util.HashSet; 32 import java.util.Set; 33 import java.util.regex.Matcher; 34 import java.util.regex.Pattern; 35 36 /** 37 * Utility functions for calling BluetoothInstrumentation on device 38 * <p> 39 * Device side BluetoothInstrumentation code can be found in AOSP at: 40 * <code>frameworks/base/core/tests/bluetoothtests</code> 41 * 42 */ 43 public class BluetoothUtils { 44 45 private static final String BT_INSTR_CMD = "am instrument -w -r -e command %s " 46 + "com.android.bluetooth.tests/android.bluetooth.BluetoothInstrumentation"; 47 private static final String SUCCESS_INSTR_OUTPUT = "INSTRUMENTATION_RESULT: result=SUCCESS"; 48 private static final String BT_GETADDR_HEADER = "INSTRUMENTATION_RESULT: address="; 49 private static final long BASE_RETRY_DELAY_MS = 60 * 1000; 50 private static final int MAX_RETRIES = 3; 51 private static final Pattern BONDED_MAC_HEADER = 52 Pattern.compile("INSTRUMENTATION_RESULT: device-\\d{2}=(.*)$"); 53 private static final String BT_STACK_CONF = "/etc/bluetooth/bt_stack.conf"; 54 public static final String BTSNOOP_API = "bluetoothConfigHciSnoopLog"; 55 public static final String BTSNOOP_CMD = "setprop persist.bluetooth.btsnoopenable "; 56 public static final String BTSNOOP_ENABLE_CMD = BTSNOOP_CMD + "true"; 57 public static final String BTSNOOP_DISABLE_CMD = BTSNOOP_CMD + "false"; 58 public static final String GOLD_BTSNOOP_LOG_PATH = "/data/misc/bluetooth/logs/btsnoop_hci.log"; 59 public static final String O_BUILD = "O"; 60 61 /** 62 * Convenience method to execute BT instrumentation command and return output 63 * 64 * @param device 65 * @param command a command string sent over to BT instrumentation, currently supported: 66 * enable, disable, unpairAll, getName, getAddress, getBondedDevices; refer to 67 * AOSP source for more details 68 * @return output of BluetoothInstrumentation 69 * @throws DeviceNotAvailableException 70 */ 71 public static String runBluetoothInstrumentation(ITestDevice device, String command) 72 throws DeviceNotAvailableException { 73 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 74 device.executeShellCommand(String.format(BT_INSTR_CMD, command), receiver); 75 String output = receiver.getOutput(); 76 CLog.v("bluetooth instrumentation sub command: %s\noutput:\n", command); 77 CLog.v(output); 78 return output; 79 } 80 81 public static boolean runBluetoothInstrumentationWithRetry(ITestDevice device, String command) 82 throws DeviceNotAvailableException { 83 for (int retry = 0; retry < MAX_RETRIES; retry++) { 84 String output = runBluetoothInstrumentation(device, command); 85 if (output.contains(SUCCESS_INSTR_OUTPUT)) { 86 return true; 87 } 88 RunUtil.getDefault().sleep(retry * BASE_RETRY_DELAY_MS); 89 } 90 return false; 91 } 92 93 /** 94 * Retries clearing of BT pairing with linear backoff 95 * @param device 96 * @throws DeviceNotAvailableException 97 */ 98 public static boolean unpairWithRetry(ITestDevice device) 99 throws DeviceNotAvailableException { 100 return runBluetoothInstrumentationWithRetry(device, "unpairAll"); 101 } 102 103 /** 104 * Retrieves BT mac of the given device 105 * 106 * @param device 107 * @return BT mac or null if not found 108 * @throws DeviceNotAvailableException 109 */ 110 public static String getBluetoothMac(ITestDevice device) throws DeviceNotAvailableException { 111 String lines[] = runBluetoothInstrumentation(device, "getAddress").split("\\r?\\n"); 112 for (String line : lines) { 113 line = line.trim(); 114 if (line.startsWith(BT_GETADDR_HEADER)) { 115 return line.substring(BT_GETADDR_HEADER.length()); 116 } 117 } 118 return null; 119 } 120 121 /** 122 * Enables bluetooth on the given device 123 * 124 * @param device 125 * @return True if enable is successful, false otherwise 126 * @throws DeviceNotAvailableException 127 */ 128 public static boolean enable(ITestDevice device) 129 throws DeviceNotAvailableException { 130 return runBluetoothInstrumentationWithRetry(device, "enable"); 131 } 132 133 /** 134 * Disables bluetooth on the given device 135 * 136 * @param device 137 * @return True if disable is successful, false otherwise 138 * @throws DeviceNotAvailableException 139 */ 140 public static boolean disable(ITestDevice device) 141 throws DeviceNotAvailableException { 142 return runBluetoothInstrumentationWithRetry(device, "disable"); 143 } 144 145 /** 146 * Returns bluetooth mac addresses of devices that the given device has bonded with 147 * 148 * @param device 149 * @return bluetooth mac addresses 150 * @throws DeviceNotAvailableException 151 */ 152 public static Set<String> getBondedDevices(ITestDevice device) 153 throws DeviceNotAvailableException { 154 String lines[] = runBluetoothInstrumentation(device, "getBondedDevices").split("\\r?\\n"); 155 return parseBondedDeviceInstrumentationOutput(lines); 156 } 157 158 /** Parses instrumentation output into mac addresses */ 159 static Set<String> parseBondedDeviceInstrumentationOutput(String[] lines) { 160 Set<String> ret = new HashSet<>(); 161 for (String line : lines) { 162 Matcher m = BONDED_MAC_HEADER.matcher(line.trim()); 163 if (m.find()) { 164 ret.add(m.group(1)); 165 } 166 } 167 return ret; 168 } 169 170 /** 171 * Confirm branch version if it is Gold or not based on build alias 172 * 173 * @param device Test device to check 174 * @throws DeviceNotAvailableException 175 */ 176 private static boolean isGoldAndAbove(ITestDevice device) throws DeviceNotAvailableException { 177 // TODO: Use API level once it is bumped up 178 int apiLevel = device.getApiLevel(); 179 if (!"REL".equals(device.getProperty("ro.build.version.codename"))) { 180 apiLevel++; 181 } 182 return apiLevel >= 25; 183 } 184 185 /** 186 * Enable btsnoop logging by sl4a call 187 * 188 * @param device 189 * @return success or not 190 * @throws DeviceNotAvailableException 191 */ 192 public static boolean enableBtsnoopLogging(ITestDevice device) 193 throws DeviceNotAvailableException { 194 if (isGoldAndAbove(device)) { 195 device.executeShellCommand(BTSNOOP_ENABLE_CMD); 196 disable(device); 197 enable(device); 198 return true; 199 } 200 return enableBtsnoopLogging(device, null); 201 } 202 203 /** 204 * Enable btsnoop logging by sl4a call 205 * 206 * @param device 207 * @param sl4aApkFile sl4a.apk file location, null if it has been installed 208 * @return success or not 209 * @throws DeviceNotAvailableException 210 */ 211 public static boolean enableBtsnoopLogging(ITestDevice device, File sl4aApkFile) 212 throws DeviceNotAvailableException { 213 Sl4aClient client = new Sl4aClient(device, sl4aApkFile); 214 return toggleBtsnoopLogging(client, true); 215 } 216 217 /** 218 * Disable btsnoop logging by sl4a call 219 * 220 * @param device 221 * @return success or not 222 * @throws DeviceNotAvailableException 223 */ 224 public static boolean disableBtsnoopLogging(ITestDevice device) 225 throws DeviceNotAvailableException { 226 if (isGoldAndAbove(device)) { 227 device.executeShellCommand(BTSNOOP_DISABLE_CMD); 228 disable(device); 229 enable(device); 230 return true; 231 } 232 return disableBtsnoopLogging(device, null); 233 } 234 235 /** 236 * Disable btsnoop logging by sl4a call 237 * 238 * @param device 239 * @param sl4aApkFile sl4a.apk file location, null if it has been installed 240 * @return success or not 241 * @throws DeviceNotAvailableException 242 */ 243 public static boolean disableBtsnoopLogging(ITestDevice device, File sl4aApkFile) 244 throws DeviceNotAvailableException { 245 Sl4aClient client = new Sl4aClient(device, sl4aApkFile); 246 return toggleBtsnoopLogging(client, false); 247 } 248 249 public static boolean toggleBtsnoopLogging(Sl4aClient client, boolean onOff) 250 throws DeviceNotAvailableException { 251 try { 252 client.startSl4A(); 253 client.rpcCall(BTSNOOP_API, onOff); 254 return true; 255 } catch (IOException | RuntimeException e) { 256 CLog.e(e); 257 return false; 258 } finally { 259 client.close(); 260 } 261 } 262 263 /** 264 * Upload snoop log file for test results 265 */ 266 public static void uploadLogFiles(ITestInvocationListener listener, ITestDevice device, 267 String type, int iteration) throws DeviceNotAvailableException { 268 File logFile = null; 269 InputStreamSource logSource = null; 270 String fileName = getBtSnoopLogFilePath(device); 271 if (fileName != null) { 272 try { 273 logFile = device.pullFile(fileName); 274 if (logFile != null) { 275 CLog.d( 276 "Sending %s %d byte file %s into the logosphere!", 277 type, logFile.length(), logFile); 278 logSource = new FileInputStreamSource(logFile); 279 listener.testLog( 280 String.format("%s_btsnoop_%d", type, iteration), 281 LogDataType.UNKNOWN, 282 logSource); 283 } 284 } finally { 285 FileUtil.deleteFile(logFile); 286 StreamUtil.cancel(logSource); 287 } 288 } else { 289 return; 290 } 291 } 292 293 /** 294 * Delete snoop log file from device 295 */ 296 public static void cleanLogFile(ITestDevice device) throws DeviceNotAvailableException { 297 String fileName = getBtSnoopLogFilePath(device); 298 if (fileName != null) { 299 device.executeShellCommand(String.format("rm %s", fileName)); 300 } else { 301 CLog.e("Not able to delete BT snoop log, file not found"); 302 } 303 } 304 305 /** 306 * Get bt snoop log file path from bt_stack.config file 307 * 308 * @param device 309 * @return THe file name for bt_snoop_log or null if it is not found 310 */ 311 public static String getBtSnoopLogFilePath(ITestDevice device) 312 throws DeviceNotAvailableException { 313 if (isGoldAndAbove(device)) { 314 return GOLD_BTSNOOP_LOG_PATH; 315 } 316 String snoopfileSetting = 317 device.executeShellCommand( 318 String.format("cat %s | grep BtSnoopFileName", BT_STACK_CONF)); 319 String[] settingItems = snoopfileSetting.split("="); 320 if (settingItems.length > 1) { 321 return settingItems[1].trim(); 322 } else { 323 CLog.e(String.format("Not able to local BT snoop log, '%s'", snoopfileSetting)); 324 return null; 325 } 326 } 327 } 328