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