1 /* 2 * Copyright (C) 2010 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 package com.android.tradefed.device; 17 18 import com.android.ddmlib.MultiLineReceiver; 19 import com.android.tradefed.error.HarnessRuntimeException; 20 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 21 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.result.error.DeviceErrorIdentifier; 24 import com.android.tradefed.util.CommandResult; 25 import com.android.tradefed.util.CommandStatus; 26 import com.android.tradefed.util.FileUtil; 27 import com.android.tradefed.util.IRunUtil; 28 import com.android.tradefed.util.RunUtil; 29 30 import com.google.common.annotations.VisibleForTesting; 31 import com.google.common.base.Strings; 32 33 import org.json.JSONException; 34 import org.json.JSONObject; 35 36 import java.io.File; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.util.ArrayList; 40 import java.util.HashMap; 41 import java.util.Iterator; 42 import java.util.LinkedHashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.concurrent.TimeUnit; 46 import java.util.regex.Matcher; 47 import java.util.regex.Pattern; 48 49 /** 50 * Helper class for manipulating wifi services on device. 51 */ 52 public class WifiHelper implements IWifiHelper { 53 54 private static final String NULL = "null"; 55 private static final String NULL_IP_ADDR = "0.0.0.0"; 56 private static final String INSTRUMENTATION_CLASS = ".WifiUtil"; 57 public static final String INSTRUMENTATION_PKG = "com.android.tradefed.utils.wifi"; 58 static final String FULL_INSTRUMENTATION_NAME = 59 String.format("%s/%s", INSTRUMENTATION_PKG, INSTRUMENTATION_CLASS); 60 61 static final String CHECK_PACKAGE_CMD = 62 String.format("dumpsys package %s", INSTRUMENTATION_PKG); 63 static final String ENABLE_WIFI_CMD = "svc wifi enable"; 64 static final String DISABLE_WIFI_CMD = "svc wifi disable"; 65 static final Pattern PACKAGE_VERSION_PAT = Pattern.compile("versionCode=(\\d*)"); 66 static final int PACKAGE_VERSION_CODE = 21; 67 68 private static final String WIFIUTIL_APK_NAME = "WifiUtil.apk"; 69 private static final String WIFIUTIL_APK_RES = "/" + WIFIUTIL_APK_NAME; 70 private static final String WIFIUTIL_APK_RES_FALLBACK = 71 "/com/android/tradefed/utils/wifi/" + WIFIUTIL_APK_NAME; 72 /** the default WifiUtil command timeout in minutes */ 73 private static final long WIFIUTIL_CMD_TIMEOUT_MINUTES = 5; 74 75 /** the default time in ms to wait for a wifi state */ 76 private static final long DEFAULT_WIFI_STATE_TIMEOUT = 200*1000; 77 78 private final ITestDevice mDevice; 79 private File mWifiUtilApkFile; 80 81 /** Tracks whether wifi helper needs to execute v2 operations. */ 82 private boolean mUseV2; 83 WifiHelper(ITestDevice device)84 public WifiHelper(ITestDevice device) throws DeviceNotAvailableException { 85 this(device, null, true); 86 } 87 WifiHelper(ITestDevice device, String wifiUtilApkPath)88 public WifiHelper(ITestDevice device, String wifiUtilApkPath) 89 throws DeviceNotAvailableException { 90 this(device, wifiUtilApkPath, true); 91 } 92 93 /** Alternative constructor that can skip the setup of the wifi apk. */ WifiHelper(ITestDevice device, String wifiUtilApkPath, boolean doSetup)94 public WifiHelper(ITestDevice device, String wifiUtilApkPath, boolean doSetup) 95 throws DeviceNotAvailableException { 96 this(device, wifiUtilApkPath, doSetup, false); 97 } 98 99 /** 100 * Constructor to specify whether to use new wifi helper v2. v2 operations do not need to 101 * install the wifi util apk. 102 */ WifiHelper(ITestDevice device, String wifiUtilApkPath, boolean doSetup, boolean useV2)103 public WifiHelper(ITestDevice device, String wifiUtilApkPath, boolean doSetup, boolean useV2) 104 throws DeviceNotAvailableException { 105 mDevice = device; 106 mUseV2 = useV2; 107 if (doSetup) { 108 ensureDeviceSetup(wifiUtilApkPath); 109 } 110 } 111 112 /** 113 * Get the {@link RunUtil} instance to use. 114 * <p/> 115 * Exposed for unit testing. 116 */ getRunUtil()117 IRunUtil getRunUtil() { 118 return RunUtil.getDefault(); 119 } 120 ensureDeviceSetup(String wifiUtilApkPath)121 void ensureDeviceSetup(String wifiUtilApkPath) throws DeviceNotAvailableException { 122 final String inst = mDevice.executeShellCommand(CHECK_PACKAGE_CMD); 123 if (inst != null) { 124 Matcher matcher = PACKAGE_VERSION_PAT.matcher(inst); 125 if (matcher.find()) { 126 try { 127 if (PACKAGE_VERSION_CODE <= Integer.parseInt(matcher.group(1))) { 128 return; 129 } 130 } catch (NumberFormatException e) { 131 CLog.w("failed to parse WifiUtil version code: %s", matcher.group(1)); 132 } 133 } 134 } 135 136 // Attempt to install utility 137 try { 138 setupWifiUtilApkFile(wifiUtilApkPath); 139 String[] extraArgs = new String[] {}; 140 if (mDevice.isBypassLowTargetSdkBlockSupported()) { 141 extraArgs = new String[] {"--bypass-low-target-sdk-block"}; 142 } 143 144 final String error = mDevice.installPackage(mWifiUtilApkFile, true, extraArgs); 145 if (error == null) { 146 // Installed successfully; good to go. 147 return; 148 } else { 149 if (error.contains("cmd: Failure calling service package") 150 || error.contains("Can't find service: package")) { 151 String message = 152 String.format( 153 "Failed to install WifiUtil utility. Device might have" 154 + " crashed, it returned: %s", error); 155 throw new DeviceRuntimeException(message, DeviceErrorIdentifier.DEVICE_CRASHED); 156 } 157 throw new HarnessRuntimeException( 158 String.format( 159 "Unable to install WifiUtil utility: %s on %s", 160 error, mDevice.getSerialNumber()), 161 DeviceErrorIdentifier.APK_INSTALLATION_FAILED); 162 } 163 } catch (IOException e) { 164 throw new RuntimeException(String.format( 165 "Failed to unpack WifiUtil utility: %s", e.getMessage())); 166 } finally { 167 // Delete the tmp file only if the APK is copied from classpath 168 if (wifiUtilApkPath == null) { 169 FileUtil.deleteFile(mWifiUtilApkFile); 170 } 171 } 172 } 173 setupWifiUtilApkFile(String wifiUtilApkPath)174 private void setupWifiUtilApkFile(String wifiUtilApkPath) throws IOException { 175 if (wifiUtilApkPath != null) { 176 mWifiUtilApkFile = new File(wifiUtilApkPath); 177 } else { 178 mWifiUtilApkFile = extractWifiUtilApk(); 179 } 180 } 181 182 /** 183 * Get the {@link File} object of the APK file. 184 * 185 * <p>Exposed for unit testing. 186 */ 187 @VisibleForTesting getWifiUtilApkFile()188 File getWifiUtilApkFile() { 189 return mWifiUtilApkFile; 190 } 191 192 /** 193 * Helper method to extract the wifi util apk from the classpath 194 */ extractWifiUtilApk()195 public static File extractWifiUtilApk() throws IOException { 196 File apkTempFile = FileUtil.createTempFile(WIFIUTIL_APK_NAME, ".apk"); 197 try { 198 InputStream apkStream = WifiHelper.class.getResourceAsStream(WIFIUTIL_APK_RES); 199 FileUtil.writeToFile(apkStream, apkTempFile); 200 } catch (IOException e) { 201 // Fallback to new path 202 InputStream apkStream = WifiHelper.class.getResourceAsStream(WIFIUTIL_APK_RES_FALLBACK); 203 FileUtil.writeToFile(apkStream, apkTempFile); 204 } 205 return apkTempFile; 206 } 207 208 /** 209 * {@inheritDoc} 210 */ 211 @Override enableWifi()212 public boolean enableWifi() throws DeviceNotAvailableException { 213 if (mUseV2) { 214 return enableWifiV2(); 215 } 216 CommandResult result = mDevice.executeShellV2Command(ENABLE_WIFI_CMD); 217 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 218 CLog.e( 219 "Failed to enable wifi. status: %s\nstdout: %s\nstderr: %s", 220 result.getStatus(), result.getStdout(), result.getStderr()); 221 } 222 // shell command does not produce any message to indicate success/failure, wait for state 223 // change to complete. 224 return waitForWifiEnabled(120000L); 225 } 226 227 /** Uses the wifi cmd to enable wifi. */ enableWifiV2()228 private boolean enableWifiV2() throws DeviceNotAvailableException { 229 CommandResult enableOutput = 230 mDevice.executeShellV2Command( 231 String.format("cmd -w wifi set-wifi-enabled enabled")); 232 if (!CommandStatus.SUCCESS.equals(enableOutput.getStatus())) { 233 CLog.w( 234 "Failed to enable wifi. stdout: %s\nstderr:%s", 235 enableOutput.getStdout(), enableOutput.getStderr()); 236 return false; 237 } 238 return waitForWifiEnabled(120000L); 239 } 240 241 /** 242 * {@inheritDoc} 243 */ 244 @Override disableWifi()245 public boolean disableWifi() throws DeviceNotAvailableException { 246 mDevice.executeShellCommand(DISABLE_WIFI_CMD); 247 // shell command does not produce any message to indicate success/failure, wait for state 248 // change to complete. 249 return waitForWifiDisabled(); 250 } 251 252 /** 253 * {@inheritDoc} 254 */ 255 @Override waitForWifiState(WifiState... expectedStates)256 public boolean waitForWifiState(WifiState... expectedStates) throws DeviceNotAvailableException { 257 return waitForWifiState(DEFAULT_WIFI_STATE_TIMEOUT, expectedStates); 258 } 259 260 /** 261 * Waits the given time until one of the expected wifi states occurs. 262 * 263 * @param expectedStates one or more wifi states to expect 264 * @param timeout max time in ms to wait 265 * @return <code>true</code> if the one of the expected states occurred. <code>false</code> if 266 * none of the states occurred before timeout is reached 267 * @throws DeviceNotAvailableException 268 */ waitForWifiState(long timeout, WifiState... expectedStates)269 boolean waitForWifiState(long timeout, WifiState... expectedStates) 270 throws DeviceNotAvailableException { 271 long startTime = System.currentTimeMillis(); 272 while (System.currentTimeMillis() < (startTime + timeout)) { 273 String state = runWifiUtil("getSupplicantState"); 274 for (WifiState expectedState : expectedStates) { 275 if (expectedState.name().equals(state)) { 276 return true; 277 } 278 } 279 getRunUtil().sleep(getPollTime()); 280 } 281 return false; 282 } 283 284 /** 285 * Gets the time to sleep between poll attempts 286 */ getPollTime()287 long getPollTime() { 288 return 1*1000; 289 } 290 291 /** 292 * Remove the network identified by an integer network id. 293 * 294 * @param networkId the network id identifying its profile in wpa_supplicant configuration 295 * @throws DeviceNotAvailableException 296 */ removeNetwork(int networkId)297 boolean removeNetwork(int networkId) throws DeviceNotAvailableException { 298 if (!asBool(runWifiUtil("removeNetwork", "id", Integer.toString(networkId)))) { 299 return false; 300 } 301 if (!asBool(runWifiUtil("saveConfiguration"))) { 302 return false; 303 } 304 return true; 305 } 306 307 /** 308 * {@inheritDoc} 309 */ 310 @Override addOpenNetwork(String ssid)311 public boolean addOpenNetwork(String ssid) throws DeviceNotAvailableException { 312 return addOpenNetwork(ssid, false); 313 } 314 315 /** 316 * {@inheritDoc} 317 */ 318 @Override addOpenNetwork(String ssid, boolean scanSsid)319 public boolean addOpenNetwork(String ssid, boolean scanSsid) 320 throws DeviceNotAvailableException { 321 int id = asInt(runWifiUtil("addOpenNetwork", "ssid", ssid, "scanSsid", 322 Boolean.toString(scanSsid))); 323 if (id < 0) { 324 return false; 325 } 326 if (!asBool(runWifiUtil("associateNetwork", "id", Integer.toString(id)))) { 327 return false; 328 } 329 if (!asBool(runWifiUtil("saveConfiguration"))) { 330 return false; 331 } 332 return true; 333 } 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override addWpaPskNetwork(String ssid, String psk)339 public boolean addWpaPskNetwork(String ssid, String psk) throws DeviceNotAvailableException { 340 return addWpaPskNetwork(ssid, psk, false); 341 } 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override addWpaPskNetwork(String ssid, String psk, boolean scanSsid)347 public boolean addWpaPskNetwork(String ssid, String psk, boolean scanSsid) 348 throws DeviceNotAvailableException { 349 int id = asInt(runWifiUtil("addWpaPskNetwork", "ssid", ssid, "psk", psk, "scan_ssid", 350 Boolean.toString(scanSsid))); 351 if (id < 0) { 352 return false; 353 } 354 if (!asBool(runWifiUtil("associateNetwork", "id", Integer.toString(id)))) { 355 return false; 356 } 357 if (!asBool(runWifiUtil("saveConfiguration"))) { 358 return false; 359 } 360 return true; 361 } 362 363 /** 364 * {@inheritDoc} 365 */ 366 @Override waitForIp(long timeout)367 public boolean waitForIp(long timeout) throws DeviceNotAvailableException { 368 long startTime = System.currentTimeMillis(); 369 370 while (System.currentTimeMillis() < (startTime + timeout)) { 371 if (hasValidIp()) { 372 return true; 373 } 374 getRunUtil().sleep(getPollTime()); 375 } 376 return false; 377 } 378 379 /** 380 * {@inheritDoc} 381 */ 382 @Override hasValidIp()383 public boolean hasValidIp() throws DeviceNotAvailableException { 384 final String ip = getIpAddress(); 385 return ip != null && !ip.isEmpty() && !NULL_IP_ADDR.equals(ip); 386 } 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override getIpAddress()392 public String getIpAddress() throws DeviceNotAvailableException { 393 return runWifiUtil("getIpAddress"); 394 } 395 396 /** 397 * {@inheritDoc} 398 */ 399 @Override getSSID()400 public String getSSID() throws DeviceNotAvailableException { 401 return runWifiUtil("getSSID"); 402 } 403 404 /** 405 * {@inheritDoc} 406 */ 407 @Override getBSSID()408 public String getBSSID() throws DeviceNotAvailableException { 409 return runWifiUtil("getBSSID"); 410 } 411 412 /** 413 * {@inheritDoc} 414 */ 415 @Override removeAllNetworks()416 public boolean removeAllNetworks() throws DeviceNotAvailableException { 417 if (!asBool(runWifiUtil("removeAllNetworks"))) { 418 return false; 419 } 420 if (!asBool(runWifiUtil("saveConfiguration"))) { 421 return false; 422 } 423 return true; 424 } 425 426 /** 427 * {@inheritDoc} 428 */ 429 @Override isWifiEnabled()430 public boolean isWifiEnabled() throws DeviceNotAvailableException { 431 if (mUseV2) { 432 return isWifiEnabledV2(); 433 } else { 434 return asBool( 435 runWifiUtil( 436 "isWifiEnabled", 2 437 /** 2 minutes timeout */ 438 )); 439 } 440 } 441 isWifiEnabledV2()442 private boolean isWifiEnabledV2() throws DeviceNotAvailableException { 443 CommandResult statusOutput = 444 mDevice.executeShellV2Command(String.format("cmd -w wifi status")); 445 if (CommandStatus.SUCCESS.equals(statusOutput.getStatus()) 446 && !statusOutput.getStdout().contains("Wifi is disabled")) { 447 return true; 448 } else { 449 return false; 450 } 451 } 452 453 /** 454 * {@inheritDoc} 455 */ 456 @Override waitForWifiEnabled()457 public boolean waitForWifiEnabled() throws DeviceNotAvailableException { 458 return waitForWifiEnabled(DEFAULT_WIFI_STATE_TIMEOUT); 459 } 460 461 @Override waitForWifiEnabled(long timeout)462 public boolean waitForWifiEnabled(long timeout) throws DeviceNotAvailableException { 463 long startTime = System.currentTimeMillis(); 464 465 while (System.currentTimeMillis() < (startTime + timeout)) { 466 if (isWifiEnabled()) { 467 return true; 468 } 469 getRunUtil().sleep(getPollTime()); 470 } 471 return false; 472 } 473 474 /** 475 * {@inheritDoc} 476 */ 477 @Override waitForWifiDisabled()478 public boolean waitForWifiDisabled() throws DeviceNotAvailableException { 479 return waitForWifiDisabled(DEFAULT_WIFI_STATE_TIMEOUT); 480 } 481 482 @Override waitForWifiDisabled(long timeout)483 public boolean waitForWifiDisabled(long timeout) throws DeviceNotAvailableException { 484 long startTime = System.currentTimeMillis(); 485 486 while (System.currentTimeMillis() < (startTime + timeout)) { 487 if (!isWifiEnabled()) { 488 return true; 489 } 490 getRunUtil().sleep(getPollTime()); 491 } 492 return false; 493 } 494 495 /** 496 * {@inheritDoc} 497 */ 498 @Override getWifiInfo()499 public Map<String, String> getWifiInfo() throws DeviceNotAvailableException { 500 if (mUseV2) { 501 return getWifiInfoV2(); 502 } 503 504 Map<String, String> info = new HashMap<>(); 505 506 final String result = runWifiUtil("getWifiInfo"); 507 if (result != null) { 508 try { 509 final JSONObject json = new JSONObject(result); 510 final Iterator<?> keys = json.keys(); 511 while (keys.hasNext()) { 512 final String key = (String)keys.next(); 513 info.put(key, json.getString(key)); 514 } 515 } catch(final JSONException e) { 516 CLog.w("Failed to parse wifi info: %s", e.getMessage()); 517 } 518 } 519 520 return info; 521 } 522 getWifiInfoV2()523 private Map<String, String> getWifiInfoV2() throws DeviceNotAvailableException { 524 CommandResult statusOutput = 525 mDevice.executeShellV2Command(String.format("cmd -w wifi status")); 526 if (CommandStatus.SUCCESS.equals(statusOutput.getStatus())) { 527 return WifiCommandUtil.parseWifiInfo(statusOutput.getStdout()); 528 } else { 529 return new LinkedHashMap<>(); 530 } 531 } 532 533 /** 534 * {@inheritDoc} 535 */ 536 @Override checkConnectivity(String urlToCheck)537 public boolean checkConnectivity(String urlToCheck) throws DeviceNotAvailableException { 538 if (mUseV2) { 539 return checkConnectivityV2(120000L); 540 } 541 return asBool(runWifiUtil("checkConnectivity", "urlToCheck", urlToCheck)); 542 } 543 checkConnectivityV2(long timeout)544 private boolean checkConnectivityV2(long timeout) throws DeviceNotAvailableException { 545 long startTime = System.currentTimeMillis(); 546 while (System.currentTimeMillis() < (startTime + timeout)) { 547 CommandResult statusOutput = 548 mDevice.executeShellV2Command(String.format("cmd -w wifi status")); 549 if (CommandStatus.SUCCESS.equals(statusOutput.getStatus()) 550 && statusOutput.getStdout().contains("Wifi is connected")) { 551 return true; 552 } 553 getRunUtil().sleep(getPollTime()); 554 } 555 CLog.w("Wifi is not connected after timeout: %d ms.", timeout); 556 return false; 557 } 558 559 /** 560 * {@inheritDoc} 561 */ 562 @Override connectToNetwork(String ssid, String psk, String urlToCheck)563 public boolean connectToNetwork(String ssid, String psk, String urlToCheck) 564 throws DeviceNotAvailableException { 565 return WifiConnectionResult.SUCCESS == connectToNetwork(ssid, psk, urlToCheck, false); 566 } 567 568 /** {@inheritDoc} */ 569 @Override connectToNetwork( String ssid, String psk, String urlToCheck, boolean scanSsid)570 public WifiConnectionResult connectToNetwork( 571 String ssid, String psk, String urlToCheck, boolean scanSsid) 572 throws DeviceNotAvailableException { 573 return connectToNetwork(ssid, psk, urlToCheck, scanSsid, null); 574 } 575 /** {@inheritDoc} */ 576 @Override connectToNetwork( String ssid, String psk, String urlToCheck, boolean scanSsid, String defaultType)577 public WifiConnectionResult connectToNetwork( 578 String ssid, String psk, String urlToCheck, boolean scanSsid, String defaultType) 579 throws DeviceNotAvailableException { 580 if (mUseV2) { 581 return connectToNetworkV2(ssid, psk, scanSsid, defaultType); 582 } 583 584 if (!enableWifi()) { 585 CLog.e("Failed to enable wifi"); 586 return WifiConnectionResult.FAILED_TO_ENABLE; 587 } 588 if (!asBool(runWifiUtil("connectToNetwork", "ssid", ssid, "psk", psk, "urlToCheck", 589 urlToCheck, "scan_ssid", Boolean.toString(scanSsid)))) { 590 return WifiConnectionResult.FAILED_TO_CONNECT; 591 } 592 return WifiConnectionResult.SUCCESS; 593 } 594 595 /** Uses the wifi connect-network cmd to connect to wifi network instead of using the apk. */ connectToNetworkV2( String ssid, String psk, boolean scanSsid, String defaultType)596 private WifiConnectionResult connectToNetworkV2( 597 String ssid, String psk, boolean scanSsid, String defaultType) 598 throws DeviceNotAvailableException { 599 if (Strings.isNullOrEmpty(ssid)) { 600 CLog.d("SSID of the wifi network can not be null or empty."); 601 return WifiConnectionResult.FAILED_TO_CONNECT; 602 } 603 if (psk == null) { 604 // psk can be empty for open networks. 605 psk = ""; 606 } 607 if (!enableWifiV2()) { 608 return WifiConnectionResult.FAILED_TO_ENABLE; 609 } 610 // after enabling wifi, wait for scan results to appear 611 List<WifiCommandUtil.ScanResult> scanResults = getScanResults(120000L); 612 if (scanResults == null) { 613 return WifiConnectionResult.FAILED_TO_CONNECT; 614 } 615 String networkType = findNetworkType(ssid, scanResults); 616 if (networkType == null) { 617 if (defaultType != null) { 618 // Use the default network type if we fail to find it. 619 CLog.d( 620 "Defaulting to a `%s` network type. Network connection may fail.", 621 defaultType); 622 networkType = defaultType; 623 } else { 624 return WifiConnectionResult.FAILED_TO_CONNECT; 625 } 626 } 627 String connectCmd = 628 String.format( 629 "cmd -w wifi connect-network %s %s %s", 630 quote(ssid), networkType, quote(psk)); 631 if (scanSsid) { 632 connectCmd += " -h"; 633 } 634 CommandResult connectOutput = mDevice.executeShellV2Command(connectCmd); 635 if (CommandStatus.SUCCESS.equals(connectOutput.getStatus()) 636 && checkConnectivityV2(120000L)) { 637 CLog.i("Successfully connected to wifi network %s", ssid); 638 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.WIFI_AP_NAME, ssid); 639 return WifiConnectionResult.SUCCESS; 640 } else { 641 CLog.w( 642 "Failed to connect to wifi. stdout: %s\nstderr:%s", 643 connectOutput.getStdout(), connectOutput.getStderr()); 644 return WifiConnectionResult.FAILED_TO_CONNECT; 645 } 646 } 647 648 /** Returns a list of scan result using the `wifi list-scan-results` command. */ getScanResults(long timeout)649 private List<WifiCommandUtil.ScanResult> getScanResults(long timeout) 650 throws DeviceNotAvailableException { 651 // start a new scan 652 mDevice.executeShellV2Command("cmd -w wifi start-scan"); 653 // we might need to wait for scan results to be available. 654 long startTime = System.currentTimeMillis(); 655 while (System.currentTimeMillis() < (startTime + timeout)) { 656 CommandResult listOutput = 657 mDevice.executeShellV2Command(String.format("cmd -w wifi list-scan-results")); 658 if (!CommandStatus.SUCCESS.equals(listOutput.getStatus())) { 659 CLog.w( 660 "Failed to list wifi scan results. stdout: %s\nstderr:%s", 661 listOutput.getStdout(), listOutput.getStderr()); 662 return null; 663 } 664 665 List<WifiCommandUtil.ScanResult> scanResults = 666 WifiCommandUtil.parseScanResults(listOutput.getStdout()); 667 if (!scanResults.isEmpty()) { 668 return scanResults; 669 } 670 CLog.d("Scan results is not available yet. Scan Results:\n%s", listOutput.getStdout()); 671 getRunUtil().sleep(getPollTime()); 672 } 673 CLog.d("Failed to find scan results after timeout: %d ms", timeout); 674 return null; 675 } 676 677 /** Returns the network type for a given Ssid. */ findNetworkType(String ssid, List<WifiCommandUtil.ScanResult> scanResults)678 private String findNetworkType(String ssid, List<WifiCommandUtil.ScanResult> scanResults) { 679 // Find the scan result for the ssid 680 for (WifiCommandUtil.ScanResult scanResult : scanResults) { 681 if (scanResult.getInfo("SSID").equals(ssid)) { 682 return WifiCommandUtil.resolveNetworkType(scanResult.getInfo("Flags")); 683 } 684 } 685 CLog.w("Failed to find scan result for ssid %s from scanResults: \n%s", ssid, scanResults); 686 return null; 687 } 688 689 /** 690 * {@inheritDoc} 691 */ 692 @Override disconnectFromNetwork()693 public boolean disconnectFromNetwork() throws DeviceNotAvailableException { 694 if (!asBool(runWifiUtil("disconnectFromNetwork"))) { 695 return false; 696 } 697 if (!disableWifi()) { 698 CLog.e("Failed to disable wifi"); 699 return false; 700 } 701 return true; 702 } 703 704 /** 705 * {@inheritDoc} 706 */ 707 @Override startMonitor(long interval, String urlToCheck)708 public boolean startMonitor(long interval, String urlToCheck) throws DeviceNotAvailableException { 709 return asBool(runWifiUtil("startMonitor", "interval", Long.toString(interval), "urlToCheck", 710 urlToCheck)); 711 } 712 713 /** 714 * {@inheritDoc} 715 */ 716 @Override stopMonitor()717 public List<Long> stopMonitor() throws DeviceNotAvailableException { 718 final String output = runWifiUtil("stopMonitor"); 719 if (output == null || output.isEmpty() || NULL.equals(output)) { 720 return new ArrayList<Long>(0); 721 } 722 723 String[] tokens = output.split(","); 724 List<Long> values = new ArrayList<Long>(tokens.length); 725 for (final String token : tokens) { 726 values.add(Long.parseLong(token)); 727 } 728 return values; 729 } 730 runWifiUtil(String method, String... args)731 private String runWifiUtil(String method, String... args) throws DeviceNotAvailableException { 732 return runWifiUtil(method, WIFIUTIL_CMD_TIMEOUT_MINUTES, args); 733 } 734 735 /** 736 * Run a WifiUtil command and return the result 737 * 738 * @param method the WifiUtil method to call 739 * @param timeout in minutes for the command 740 * @param args a flat list of [arg-name, value] pairs to pass 741 * @return The value of the result field in the output, or <code>null</code> if result could not 742 * be parsed 743 */ runWifiUtil(String method, long timeout, String... args)744 private String runWifiUtil(String method, long timeout, String... args) 745 throws DeviceNotAvailableException { 746 final String cmd = buildWifiUtilCmd(method, args); 747 748 WifiUtilOutput parser = new WifiUtilOutput(); 749 mDevice.executeShellCommand(cmd, parser, timeout, timeout, TimeUnit.MINUTES, 0); 750 if (parser.getError() != null) { 751 String errorMessage = 752 String.format( 753 "Failed to %s due to: '%s'. See logcat for details.", 754 method, parser.getError()); 755 CLog.e(errorMessage); 756 } 757 return parser.getResult(); 758 } 759 760 /** 761 * Build and return a WifiUtil command for the specified method and args 762 * 763 * @param method the WifiUtil method to call 764 * @param args a flat list of [arg-name, value] pairs to pass 765 * @return the command to be executed on the device shell 766 */ buildWifiUtilCmd(String method, String... args)767 static String buildWifiUtilCmd(String method, String... args) { 768 Map<String, String> argMap = new HashMap<String, String>(); 769 argMap.put("method", method); 770 if ((args.length & 0x1) == 0x1) { 771 throw new IllegalArgumentException( 772 "args should have even length, consisting of key and value pairs"); 773 } 774 for (int i = 0; i < args.length; i += 2) { 775 // Skip null parameters 776 if (args[i+1] == null) { 777 continue; 778 } 779 argMap.put(args[i], args[i+1]); 780 } 781 return buildWifiUtilCmdFromMap(argMap); 782 } 783 784 /** 785 * Build and return a WifiUtil command for the specified args 786 * 787 * @param args A Map of (arg-name, value) pairs to pass as "-e" arguments to the `am` command 788 * @return the commadn to be executed on the device shell 789 */ buildWifiUtilCmdFromMap(Map<String, String> args)790 static String buildWifiUtilCmdFromMap(Map<String, String> args) { 791 StringBuilder sb = new StringBuilder("am instrument"); 792 793 for (Map.Entry<String, String> arg : args.entrySet()) { 794 sb.append(" -e "); 795 sb.append(arg.getKey()); 796 sb.append(" "); 797 sb.append(quote(arg.getValue())); 798 } 799 800 sb.append(" -w "); 801 sb.append(INSTRUMENTATION_PKG); 802 sb.append("/"); 803 sb.append(INSTRUMENTATION_CLASS); 804 805 return sb.toString(); 806 } 807 808 /** 809 * Helper function to convert a String to an Integer 810 */ asInt(String str)811 private static int asInt(String str) { 812 if (str == null) { 813 return -1; 814 } 815 try { 816 return Integer.parseInt(str); 817 } catch (NumberFormatException e) { 818 return -1; 819 } 820 } 821 822 /** 823 * Helper function to convert a String to a boolean. Maps "true" to true, and everything else 824 * to false. 825 */ asBool(String str)826 private static boolean asBool(String str) { 827 return "true".equals(str); 828 } 829 830 /** 831 * Helper function to wrap the specified String in single-quotes to prevent shell interpretation 832 */ quote(String str)833 private static String quote(String str) { 834 return "'" + str.replace("'", "'\\''") + "'"; 835 } 836 837 /** 838 * Processes the output of a WifiUtil invocation 839 */ 840 private static class WifiUtilOutput extends MultiLineReceiver { 841 private static final Pattern RESULT_PAT = 842 Pattern.compile("INSTRUMENTATION_RESULT: result=(.*)"); 843 private static final Pattern ERROR_PAT = 844 Pattern.compile("INSTRUMENTATION_RESULT: error=(.*)"); 845 846 private String mResult = null; 847 private String mError = null; 848 849 /** 850 * {@inheritDoc} 851 */ 852 @Override processNewLines(String[] lines)853 public void processNewLines(String[] lines) { 854 for (String line : lines) { 855 Matcher resultMatcher = RESULT_PAT.matcher(line); 856 if (resultMatcher.matches()) { 857 mResult = resultMatcher.group(1); 858 continue; 859 } 860 861 Matcher errorMatcher = ERROR_PAT.matcher(line); 862 if (errorMatcher.matches()) { 863 mError = errorMatcher.group(1); 864 } 865 } 866 } 867 868 /** 869 * Return the result flag parsed from instrumentation output. <code>null</code> is returned 870 * if result output was not present. 871 */ getResult()872 String getResult() { 873 return mResult; 874 } 875 getError()876 String getError() { 877 return mError; 878 } 879 880 /** 881 * {@inheritDoc} 882 */ 883 @Override isCancelled()884 public boolean isCancelled() { 885 return false; 886 } 887 } 888 889 /** {@inheritDoc} */ 890 @Override cleanUp()891 public void cleanUp() throws DeviceNotAvailableException { 892 String output = mDevice.uninstallPackage(INSTRUMENTATION_PKG); 893 if (output != null) { 894 CLog.w("Error '%s' occurred when uninstalling %s", output, INSTRUMENTATION_PKG); 895 } else { 896 CLog.d("Successfully clean up WifiHelper."); 897 } 898 } 899 } 900 901