Home | History | Annotate | Download | only in device
      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.log.LogUtil.CLog;
     20 import com.android.tradefed.util.FileUtil;
     21 import com.android.tradefed.util.IRunUtil;
     22 import com.android.tradefed.util.RunUtil;
     23 
     24 import com.google.common.annotations.VisibleForTesting;
     25 
     26 import org.json.JSONException;
     27 import org.json.JSONObject;
     28 
     29 import java.io.File;
     30 import java.io.IOException;
     31 import java.io.InputStream;
     32 import java.util.ArrayList;
     33 import java.util.HashMap;
     34 import java.util.Iterator;
     35 import java.util.List;
     36 import java.util.Map;
     37 import java.util.concurrent.TimeUnit;
     38 import java.util.regex.Matcher;
     39 import java.util.regex.Pattern;
     40 
     41 /**
     42  * Helper class for manipulating wifi services on device.
     43  */
     44 public class WifiHelper implements IWifiHelper {
     45 
     46     private static final String NULL = "null";
     47     private static final String NULL_IP_ADDR = "0.0.0.0";
     48     private static final String INSTRUMENTATION_CLASS = ".WifiUtil";
     49     public static final String INSTRUMENTATION_PKG = "com.android.tradefed.utils.wifi";
     50     static final String FULL_INSTRUMENTATION_NAME =
     51             String.format("%s/%s", INSTRUMENTATION_PKG, INSTRUMENTATION_CLASS);
     52 
     53     static final String CHECK_PACKAGE_CMD =
     54             String.format("dumpsys package %s", INSTRUMENTATION_PKG);
     55     static final Pattern PACKAGE_VERSION_PAT = Pattern.compile("versionCode=(\\d*)");
     56     static final int PACKAGE_VERSION_CODE = 21;
     57 
     58     private static final String WIFIUTIL_APK_NAME = "WifiUtil.apk";
     59     /** the default WifiUtil command timeout in minutes */
     60     private static final long WIFIUTIL_CMD_TIMEOUT_MINUTES = 5;
     61 
     62     /** the default time in ms to wait for a wifi state */
     63     private static final long DEFAULT_WIFI_STATE_TIMEOUT = 30*1000;
     64 
     65     private final ITestDevice mDevice;
     66     private File mWifiUtilApkFile;
     67 
     68     public WifiHelper(ITestDevice device) throws DeviceNotAvailableException {
     69         this(device, null, true);
     70     }
     71 
     72     public WifiHelper(ITestDevice device, String wifiUtilApkPath)
     73             throws DeviceNotAvailableException {
     74         this(device, wifiUtilApkPath, true);
     75     }
     76 
     77     /** Alternative constructor that can skip the setup of the wifi apk. */
     78     public WifiHelper(ITestDevice device, String wifiUtilApkPath, boolean doSetup)
     79             throws DeviceNotAvailableException {
     80         mDevice = device;
     81         if (doSetup) {
     82             ensureDeviceSetup(wifiUtilApkPath);
     83         }
     84     }
     85 
     86     /**
     87      * Get the {@link RunUtil} instance to use.
     88      * <p/>
     89      * Exposed for unit testing.
     90      */
     91     IRunUtil getRunUtil() {
     92         return RunUtil.getDefault();
     93     }
     94 
     95     void ensureDeviceSetup(String wifiUtilApkPath) throws DeviceNotAvailableException {
     96         final String inst = mDevice.executeShellCommand(CHECK_PACKAGE_CMD);
     97         if (inst != null) {
     98             Matcher matcher = PACKAGE_VERSION_PAT.matcher(inst);
     99             if (matcher.find()) {
    100                 try {
    101                     if (PACKAGE_VERSION_CODE <= Integer.parseInt(matcher.group(1))) {
    102                         return;
    103                     }
    104                 } catch (NumberFormatException e) {
    105                     CLog.w("failed to parse WifiUtil version code: %s", matcher.group(1));
    106                 }
    107             }
    108         }
    109 
    110         // Attempt to install utility
    111         try {
    112             setupWifiUtilApkFile(wifiUtilApkPath);
    113 
    114             final String error = mDevice.installPackage(mWifiUtilApkFile, true);
    115             if (error == null) {
    116                 // Installed successfully; good to go.
    117                 return;
    118             } else {
    119                 throw new RuntimeException(String.format(
    120                         "Unable to install WifiUtil utility: %s", error));
    121             }
    122         } catch (IOException e) {
    123             throw new RuntimeException(String.format(
    124                     "Failed to unpack WifiUtil utility: %s", e.getMessage()));
    125         } finally {
    126             // Delete the tmp file only if the APK is copied from classpath
    127             if (wifiUtilApkPath == null) {
    128                 FileUtil.deleteFile(mWifiUtilApkFile);
    129             }
    130         }
    131     }
    132 
    133     private void setupWifiUtilApkFile(String wifiUtilApkPath) throws IOException {
    134         if (wifiUtilApkPath != null) {
    135             mWifiUtilApkFile = new File(wifiUtilApkPath);
    136         } else {
    137             mWifiUtilApkFile = extractWifiUtilApk();
    138         }
    139     }
    140 
    141     /**
    142      * Get the {@link File} object of the APK file.
    143      *
    144      * <p>Exposed for unit testing.
    145      */
    146     @VisibleForTesting
    147     File getWifiUtilApkFile() {
    148         return mWifiUtilApkFile;
    149     }
    150 
    151     /**
    152      * Helper method to extract the wifi util apk from the classpath
    153      */
    154     public static File extractWifiUtilApk() throws IOException {
    155         File apkTempFile;
    156         apkTempFile = FileUtil.createTempFile(WIFIUTIL_APK_NAME, ".apk");
    157         InputStream apkStream = WifiHelper.class.getResourceAsStream(
    158             String.format("/apks/wifiutil/%s", WIFIUTIL_APK_NAME));
    159         FileUtil.writeToFile(apkStream, apkTempFile);
    160         return apkTempFile;
    161     }
    162 
    163     /**
    164      * {@inheritDoc}
    165      */
    166     @Override
    167     public boolean enableWifi() throws DeviceNotAvailableException {
    168         return asBool(runWifiUtil("enableWifi"));
    169     }
    170 
    171     /**
    172      * {@inheritDoc}
    173      */
    174     @Override
    175     public boolean disableWifi() throws DeviceNotAvailableException {
    176         return asBool(runWifiUtil("disableWifi"));
    177     }
    178 
    179     /**
    180      * {@inheritDoc}
    181      */
    182     @Override
    183     public boolean waitForWifiState(WifiState... expectedStates) throws DeviceNotAvailableException {
    184         return waitForWifiState(DEFAULT_WIFI_STATE_TIMEOUT, expectedStates);
    185     }
    186 
    187     /**
    188      * Waits the given time until one of the expected wifi states occurs.
    189      *
    190      * @param expectedStates one or more wifi states to expect
    191      * @param timeout max time in ms to wait
    192      * @return <code>true</code> if the one of the expected states occurred. <code>false</code> if
    193      *         none of the states occurred before timeout is reached
    194      * @throws DeviceNotAvailableException
    195      */
    196      boolean waitForWifiState(long timeout, WifiState... expectedStates)
    197             throws DeviceNotAvailableException {
    198         long startTime = System.currentTimeMillis();
    199         while (System.currentTimeMillis() < (startTime + timeout)) {
    200             String state = runWifiUtil("getSupplicantState");
    201             for (WifiState expectedState : expectedStates) {
    202                 if (expectedState.name().equals(state)) {
    203                     return true;
    204                 }
    205             }
    206             getRunUtil().sleep(getPollTime());
    207         }
    208         return false;
    209     }
    210 
    211     /**
    212      * Gets the time to sleep between poll attempts
    213      */
    214     long getPollTime() {
    215         return 1*1000;
    216     }
    217 
    218     /**
    219      * Remove the network identified by an integer network id.
    220      *
    221      * @param networkId the network id identifying its profile in wpa_supplicant configuration
    222      * @throws DeviceNotAvailableException
    223      */
    224     boolean removeNetwork(int networkId) throws DeviceNotAvailableException {
    225         if (!asBool(runWifiUtil("removeNetwork", "id", Integer.toString(networkId)))) {
    226             return false;
    227         }
    228         if (!asBool(runWifiUtil("saveConfiguration"))) {
    229             return false;
    230         }
    231         return true;
    232     }
    233 
    234     /**
    235      * {@inheritDoc}
    236      */
    237     @Override
    238     public boolean addOpenNetwork(String ssid) throws DeviceNotAvailableException {
    239         return addOpenNetwork(ssid, false);
    240     }
    241 
    242     /**
    243      * {@inheritDoc}
    244      */
    245     @Override
    246     public boolean addOpenNetwork(String ssid, boolean scanSsid)
    247             throws DeviceNotAvailableException {
    248         int id = asInt(runWifiUtil("addOpenNetwork", "ssid", ssid, "scanSsid",
    249                 Boolean.toString(scanSsid)));
    250         if (id < 0) {
    251             return false;
    252         }
    253         if (!asBool(runWifiUtil("associateNetwork", "id", Integer.toString(id)))) {
    254             return false;
    255         }
    256         if (!asBool(runWifiUtil("saveConfiguration"))) {
    257             return false;
    258         }
    259         return true;
    260     }
    261 
    262     /**
    263      * {@inheritDoc}
    264      */
    265     @Override
    266     public boolean addWpaPskNetwork(String ssid, String psk) throws DeviceNotAvailableException {
    267         return addWpaPskNetwork(ssid, psk, false);
    268     }
    269 
    270     /**
    271      * {@inheritDoc}
    272      */
    273     @Override
    274     public boolean addWpaPskNetwork(String ssid, String psk, boolean scanSsid)
    275             throws DeviceNotAvailableException {
    276         int id = asInt(runWifiUtil("addWpaPskNetwork", "ssid", ssid, "psk", psk, "scan_ssid",
    277                 Boolean.toString(scanSsid)));
    278         if (id < 0) {
    279             return false;
    280         }
    281         if (!asBool(runWifiUtil("associateNetwork", "id", Integer.toString(id)))) {
    282             return false;
    283         }
    284         if (!asBool(runWifiUtil("saveConfiguration"))) {
    285             return false;
    286         }
    287         return true;
    288     }
    289 
    290     /**
    291      * {@inheritDoc}
    292      */
    293     @Override
    294     public boolean waitForIp(long timeout) throws DeviceNotAvailableException {
    295         long startTime = System.currentTimeMillis();
    296 
    297         while (System.currentTimeMillis() < (startTime + timeout)) {
    298             if (hasValidIp()) {
    299                 return true;
    300             }
    301             getRunUtil().sleep(getPollTime());
    302         }
    303         return false;
    304     }
    305 
    306     /**
    307      * {@inheritDoc}
    308      */
    309     @Override
    310     public boolean hasValidIp() throws DeviceNotAvailableException {
    311         final String ip = getIpAddress();
    312         return ip != null && !ip.isEmpty() && !NULL_IP_ADDR.equals(ip);
    313     }
    314 
    315     /**
    316      * {@inheritDoc}
    317      */
    318     @Override
    319     public String getIpAddress() throws DeviceNotAvailableException {
    320         return runWifiUtil("getIpAddress");
    321     }
    322 
    323     /**
    324      * {@inheritDoc}
    325      */
    326     @Override
    327     public String getSSID() throws DeviceNotAvailableException {
    328         return runWifiUtil("getSSID");
    329     }
    330 
    331     /**
    332      * {@inheritDoc}
    333      */
    334     @Override
    335     public String getBSSID() throws DeviceNotAvailableException {
    336         return runWifiUtil("getBSSID");
    337     }
    338 
    339     /**
    340      * {@inheritDoc}
    341      */
    342     @Override
    343     public boolean removeAllNetworks() throws DeviceNotAvailableException {
    344         if (!asBool(runWifiUtil("removeAllNetworks"))) {
    345             return false;
    346         }
    347         if (!asBool(runWifiUtil("saveConfiguration"))) {
    348             return false;
    349         }
    350         return true;
    351     }
    352 
    353     /**
    354      * {@inheritDoc}
    355      */
    356     @Override
    357     public boolean isWifiEnabled() throws DeviceNotAvailableException {
    358         return asBool(runWifiUtil("isWifiEnabled"));
    359     }
    360 
    361     /**
    362      * {@inheritDoc}
    363      */
    364     @Override
    365     public boolean waitForWifiEnabled() throws DeviceNotAvailableException {
    366         return waitForWifiEnabled(DEFAULT_WIFI_STATE_TIMEOUT);
    367     }
    368 
    369     @Override
    370     public boolean waitForWifiEnabled(long timeout) throws DeviceNotAvailableException {
    371         long startTime = System.currentTimeMillis();
    372 
    373         while (System.currentTimeMillis() < (startTime + timeout)) {
    374             if (isWifiEnabled()) {
    375                 return true;
    376             }
    377             getRunUtil().sleep(getPollTime());
    378         }
    379         return false;
    380     }
    381 
    382     /**
    383      * {@inheritDoc}
    384      */
    385     @Override
    386     public boolean waitForWifiDisabled() throws DeviceNotAvailableException {
    387         return waitForWifiDisabled(DEFAULT_WIFI_STATE_TIMEOUT);
    388     }
    389 
    390     @Override
    391     public boolean waitForWifiDisabled(long timeout) throws DeviceNotAvailableException {
    392         long startTime = System.currentTimeMillis();
    393 
    394         while (System.currentTimeMillis() < (startTime + timeout)) {
    395             if (!isWifiEnabled()) {
    396                 return true;
    397             }
    398             getRunUtil().sleep(getPollTime());
    399         }
    400         return false;
    401     }
    402 
    403     /**
    404      * {@inheritDoc}
    405      */
    406     @Override
    407     public Map<String, String> getWifiInfo() throws DeviceNotAvailableException {
    408         Map<String, String> info = new HashMap<>();
    409 
    410         final String result = runWifiUtil("getWifiInfo");
    411         if (result != null) {
    412             try {
    413                 final JSONObject json = new JSONObject(result);
    414                 final Iterator<?> keys = json.keys();
    415                 while (keys.hasNext()) {
    416                     final String key = (String)keys.next();
    417                     info.put(key, json.getString(key));
    418                 }
    419             } catch(final JSONException e) {
    420                 CLog.w("Failed to parse wifi info: %s", e.getMessage());
    421             }
    422         }
    423 
    424         return info;
    425     }
    426 
    427     /**
    428      * {@inheritDoc}
    429      */
    430     @Override
    431     public boolean checkConnectivity(String urlToCheck) throws DeviceNotAvailableException {
    432         return asBool(runWifiUtil("checkConnectivity", "urlToCheck", urlToCheck));
    433     }
    434 
    435     /**
    436      * {@inheritDoc}
    437      */
    438     @Override
    439     public boolean connectToNetwork(String ssid, String psk, String urlToCheck)
    440             throws DeviceNotAvailableException {
    441         return connectToNetwork(ssid, psk, urlToCheck, false);
    442     }
    443 
    444     /**
    445      * {@inheritDoc}
    446      */
    447     @Override
    448     public boolean connectToNetwork(String ssid, String psk, String urlToCheck,
    449             boolean scanSsid) throws DeviceNotAvailableException {
    450         return asBool(runWifiUtil("connectToNetwork", "ssid", ssid, "psk", psk, "urlToCheck",
    451                 urlToCheck, "scan_ssid", Boolean.toString(scanSsid)));
    452     }
    453 
    454     /**
    455      * {@inheritDoc}
    456      */
    457     @Override
    458     public boolean disconnectFromNetwork() throws DeviceNotAvailableException {
    459         return asBool(runWifiUtil("disconnectFromNetwork"));
    460     }
    461 
    462     /**
    463      * {@inheritDoc}
    464      */
    465     @Override
    466     public boolean startMonitor(long interval, String urlToCheck) throws DeviceNotAvailableException {
    467         return asBool(runWifiUtil("startMonitor", "interval", Long.toString(interval), "urlToCheck",
    468                 urlToCheck));
    469     }
    470 
    471     /**
    472      * {@inheritDoc}
    473      */
    474     @Override
    475     public List<Long> stopMonitor() throws DeviceNotAvailableException {
    476         final String output = runWifiUtil("stopMonitor");
    477         if (output == null || output.isEmpty() || NULL.equals(output)) {
    478             return new ArrayList<Long>(0);
    479         }
    480 
    481         String[] tokens = output.split(",");
    482         List<Long> values = new ArrayList<Long>(tokens.length);
    483         for (final String token : tokens) {
    484             values.add(Long.parseLong(token));
    485         }
    486         return values;
    487     }
    488 
    489     /**
    490      * Run a WifiUtil command and return the result
    491      *
    492      * @param method the WifiUtil method to call
    493      * @param args a flat list of [arg-name, value] pairs to pass
    494      * @return The value of the result field in the output, or <code>null</code> if result could
    495      * not be parsed
    496      */
    497     private String runWifiUtil(String method, String... args) throws DeviceNotAvailableException {
    498         final String cmd = buildWifiUtilCmd(method, args);
    499 
    500         WifiUtilOutput parser = new WifiUtilOutput();
    501         mDevice.executeShellCommand(cmd, parser, WIFIUTIL_CMD_TIMEOUT_MINUTES, TimeUnit.MINUTES, 0);
    502         if (parser.getError() != null) {
    503             CLog.e(parser.getError());
    504         }
    505         return parser.getResult();
    506     }
    507 
    508     /**
    509      * Build and return a WifiUtil command for the specified method and args
    510      *
    511      * @param method the WifiUtil method to call
    512      * @param args a flat list of [arg-name, value] pairs to pass
    513      * @return the command to be executed on the device shell
    514      */
    515     static String buildWifiUtilCmd(String method, String... args) {
    516         Map<String, String> argMap = new HashMap<String, String>();
    517         argMap.put("method", method);
    518         if ((args.length & 0x1) == 0x1) {
    519             throw new IllegalArgumentException(
    520                     "args should have even length, consisting of key and value pairs");
    521         }
    522         for (int i = 0; i < args.length; i += 2) {
    523             // Skip null parameters
    524             if (args[i+1] == null) {
    525                 continue;
    526             }
    527             argMap.put(args[i], args[i+1]);
    528         }
    529         return buildWifiUtilCmdFromMap(argMap);
    530     }
    531 
    532     /**
    533      * Build and return a WifiUtil command for the specified args
    534      *
    535      * @param args A Map of (arg-name, value) pairs to pass as "-e" arguments to the `am` command
    536      * @return the commadn to be executed on the device shell
    537      */
    538     static String buildWifiUtilCmdFromMap(Map<String, String> args) {
    539         StringBuilder sb = new StringBuilder("am instrument");
    540 
    541         for (Map.Entry<String, String> arg : args.entrySet()) {
    542             sb.append(" -e ");
    543             sb.append(arg.getKey());
    544             sb.append(" ");
    545             sb.append(quote(arg.getValue()));
    546         }
    547 
    548         sb.append(" -w ");
    549         sb.append(INSTRUMENTATION_PKG);
    550         sb.append("/");
    551         sb.append(INSTRUMENTATION_CLASS);
    552 
    553         return sb.toString();
    554     }
    555 
    556     /**
    557      * Helper function to convert a String to an Integer
    558      */
    559     private static int asInt(String str) {
    560         if (str == null) {
    561             return -1;
    562         }
    563         try {
    564             return Integer.parseInt(str);
    565         } catch (NumberFormatException e) {
    566             return -1;
    567         }
    568     }
    569 
    570     /**
    571      * Helper function to convert a String to a boolean.  Maps "true" to true, and everything else
    572      * to false.
    573      */
    574     private static boolean asBool(String str) {
    575         return "true".equals(str);
    576     }
    577 
    578     /**
    579      * Helper function to wrap the specified String in double-quotes to prevent shell interpretation
    580      */
    581     private static String quote(String str) {
    582         return String.format("\"%s\"", str);
    583     }
    584 
    585     /**
    586      * Processes the output of a WifiUtil invocation
    587      */
    588     private static class WifiUtilOutput extends MultiLineReceiver {
    589         private static final Pattern RESULT_PAT =
    590                 Pattern.compile("INSTRUMENTATION_RESULT: result=(.*)");
    591         private static final Pattern ERROR_PAT =
    592                 Pattern.compile("INSTRUMENTATION_RESULT: error=(.*)");
    593 
    594         private String mResult = null;
    595         private String mError = null;
    596 
    597         /**
    598          * {@inheritDoc}
    599          */
    600         @Override
    601         public void processNewLines(String[] lines) {
    602             for (String line : lines) {
    603                 Matcher resultMatcher = RESULT_PAT.matcher(line);
    604                 if (resultMatcher.matches()) {
    605                     mResult = resultMatcher.group(1);
    606                     continue;
    607                 }
    608 
    609                 Matcher errorMatcher = ERROR_PAT.matcher(line);
    610                 if (errorMatcher.matches()) {
    611                     mError = errorMatcher.group(1);
    612                 }
    613             }
    614         }
    615 
    616         /**
    617          * Return the result flag parsed from instrumentation output. <code>null</code> is returned
    618          * if result output was not present.
    619          */
    620         String getResult() {
    621             return mResult;
    622         }
    623 
    624         String getError() {
    625             return mError;
    626         }
    627 
    628         /**
    629          * {@inheritDoc}
    630          */
    631         @Override
    632         public boolean isCancelled() {
    633             return false;
    634         }
    635     }
    636 
    637     /** {@inheritDoc} */
    638     @Override
    639     public void cleanUp() throws DeviceNotAvailableException {
    640         String output = mDevice.uninstallPackage(INSTRUMENTATION_PKG);
    641         if (output != null) {
    642             CLog.w("Error '%s' occurred when uninstalling %s", output, INSTRUMENTATION_PKG);
    643         } else {
    644             CLog.d("Successfully clean up WifiHelper.");
    645         }
    646     }
    647 }
    648 
    649