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