• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.utils.wifi;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.net.wifi.SupplicantState;
22 import android.net.wifi.WifiConfiguration;
23 import android.net.wifi.WifiInfo;
24 import android.net.wifi.WifiManager;
25 import android.os.SystemClock;
26 import android.util.Log;
27 
28 import org.json.JSONException;
29 import org.json.JSONObject;
30 
31 import java.io.IOException;
32 import java.net.HttpURLConnection;
33 import java.net.MalformedURLException;
34 import java.net.URL;
35 import java.util.BitSet;
36 import java.util.List;
37 import java.util.concurrent.Callable;
38 
39 /**
40  * A helper class to connect to wifi networks.
41  */
42 public class WifiConnector {
43 
44     private static final String TAG = WifiConnector.class.getSimpleName();
45     private static final long DEFAULT_TIMEOUT = 120 * 1000;
46     private static final long DEFAULT_WAIT_TIME = 5 * 1000;
47     private static final long POLL_TIME = 1000;
48 
49     private Context mContext;
50     private WifiManager mWifiManager;
51 
52     /**
53      * Thrown when an error occurs while manipulating Wi-Fi services.
54      */
55     public static class WifiException extends Exception {
56 
WifiException(String msg)57         public WifiException(String msg) {
58             super(msg);
59         }
60 
WifiException(String msg, Throwable cause)61         public WifiException(String msg, Throwable cause) {
62             super(msg, cause);
63         }
64 
65     }
66 
WifiConnector(final Context context)67     public WifiConnector(final Context context) {
68         mContext = context;
69         mWifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
70     }
71 
quote(String str)72     private static String quote(String str) {
73         return String.format("\"%s\"", str);
74     }
75 
76     /**
77      * Waits until an expected condition is satisfied for {@code timeout}.
78      *
79      * @param checker a <code>Callable</code> to check the expected condition
80      * @param description a description of what this callable is doing
81      * @param timeout the duration to wait (millis) for the expected condition
82      * @throws WifiException if DEFAULT_TIMEOUT expires
83      * @return time in millis spent waiting
84      */
waitForCallable(final Callable<Boolean> checker, final String description, final long timeout)85     private long waitForCallable(final Callable<Boolean> checker, final String description,
86             final long timeout)
87             throws WifiException {
88         if (timeout <= 0) {
89             throw new WifiException(
90                 String.format("Failed %s due to invalid timeout (%d ms)", description, timeout));
91         }
92         long startTime = SystemClock.uptimeMillis();
93         long endTime = startTime + timeout;
94         try {
95             while (SystemClock.uptimeMillis() < endTime) {
96                 if (checker.call()) {
97                     long elapsed = SystemClock.uptimeMillis() - startTime;
98                     Log.i(TAG, String.format(
99                         "Time elapsed waiting for %s: %d ms", description, elapsed));
100                     return elapsed;
101                 }
102                 Thread.sleep(POLL_TIME);
103             }
104         } catch (final Exception e) {
105             throw new WifiException("failed to wait for callable", e);
106         }
107         throw new WifiException(
108             String.format("Failed %s due to exceeding timeout (%d ms)", description, timeout));
109     }
110 
waitForCallable(final Callable<Boolean> checker, final String description)111     private void waitForCallable(final Callable<Boolean> checker, final String description)
112             throws WifiException {
113         waitForCallable(checker, description, DEFAULT_TIMEOUT);
114     }
115 
116     /**
117      * Adds a Wi-Fi network configuration.
118      *
119      * @param ssid SSID of a Wi-Fi network
120      * @param psk PSK(Pre-Shared Key) of a Wi-Fi network. This can be null if the given SSID is for
121      *            an open network.
122      * @return the network ID of a new network configuration
123      * @throws WifiException if the operation fails
124      */
addNetwork(final String ssid, final String psk, final boolean scanSsid)125     public int addNetwork(final String ssid, final String psk, final boolean scanSsid)
126             throws WifiException {
127         // Skip adding network if it's already added in the device
128         // TODO: Fix the permission issue for the APK to add/update already added network
129         int networkId = getNetworkId(ssid);
130         if (networkId >= 0) {
131             return networkId;
132         }
133         final WifiConfiguration config = new WifiConfiguration();
134         // A string SSID _must_ be enclosed in double-quotation marks
135         config.SSID = quote(ssid);
136 
137         if (scanSsid) {
138             config.hiddenSSID = true;
139         }
140 
141         if (psk == null) {
142             // KeyMgmt should be NONE only
143             final BitSet keymgmt = new BitSet();
144             keymgmt.set(WifiConfiguration.KeyMgmt.NONE);
145             config.allowedKeyManagement = keymgmt;
146         } else {
147             config.preSharedKey = quote(psk);
148         }
149         networkId = mWifiManager.addNetwork(config);
150         if (-1 == networkId) {
151             throw new WifiException("failed to add network");
152         }
153 
154         return networkId;
155     }
156 
getNetworkId(String ssid)157     private int getNetworkId(String ssid) {
158         List<WifiConfiguration> netlist = mWifiManager.getConfiguredNetworks();
159         for (WifiConfiguration config : netlist) {
160             if (quote(ssid).equals(config.SSID)) {
161                 return config.networkId;
162             }
163         }
164         return -1;
165     }
166 
167     /**
168      * Removes all Wi-Fi network configurations.
169      *
170      * @param throwIfFail <code>true</code> if a caller wants an exception to be thrown when the
171      *            operation fails. Otherwise <code>false</code>.
172      * @throws WifiException if the operation fails
173      */
removeAllNetworks(boolean throwIfFail)174     public void removeAllNetworks(boolean throwIfFail) throws WifiException {
175         List<WifiConfiguration> netlist = mWifiManager.getConfiguredNetworks();
176         if (netlist != null) {
177             int failCount = 0;
178             for (WifiConfiguration config : netlist) {
179                 if (!mWifiManager.removeNetwork(config.networkId)) {
180                     Log.w(TAG, String.format("failed to remove network id %d (SSID = %s)",
181                             config.networkId, config.SSID));
182                     failCount++;
183                 }
184             }
185             if (0 < failCount && throwIfFail) {
186                 throw new WifiException("failed to remove all networks.");
187             }
188         }
189     }
190 
191     /**
192      * Check network connectivity by sending a HTTP request to a given URL.
193      *
194      * @param urlToCheck URL to send a test request to
195      * @return <code>true</code> if the test request succeeds. Otherwise <code>false</code>.
196      */
checkConnectivity(final String urlToCheck)197     public static boolean checkConnectivity(final String urlToCheck) {
198         URL url = null;
199         try {
200             url = new URL(urlToCheck);
201         } catch (MalformedURLException e) {
202             throw new RuntimeException("Malformed URL", e);
203         }
204         HttpURLConnection urlConnection = null;
205         try {
206             urlConnection = (HttpURLConnection) url.openConnection();
207             urlConnection.connect();
208         } catch (final IOException e) {
209             return false;
210         } finally {
211             if (urlConnection != null) {
212                 urlConnection.disconnect();
213             }
214         }
215         return true;
216     }
217 
218     /**
219      * Connects a device to a given Wi-Fi network and check connectivity.
220      *
221      * @param ssid SSID of a Wi-Fi network
222      * @param psk PSK of a Wi-Fi network
223      * @param urlToCheck URL to use when checking connectivity
224      * @param connectTimeout duration in seconds to wait for connecting to the network or
225               {@code DEFAULT_TIMEOUT} millis if -1 is passed.
226      * @param scanSsid whether to scan for hidden SSID for this network
227      * @throws WifiException if the operation fails
228      */
connectToNetwork(final String ssid, final String psk, final String urlToCheck, long connectTimeout, final boolean scanSsid)229     public void connectToNetwork(final String ssid, final String psk, final String urlToCheck,
230             long connectTimeout, final boolean scanSsid)
231             throws WifiException {
232         if (!mWifiManager.isWifiEnabled()) {
233             throw new WifiException("wifi not enabled");
234         }
235 
236         updateLastNetwork(ssid, psk, scanSsid);
237 
238         connectTimeout = connectTimeout == -1 ? DEFAULT_TIMEOUT : (connectTimeout * 1000);
239         long timeSpent;
240         timeSpent = waitForCallable(new Callable<Boolean>() {
241                 @Override
242                 public Boolean call() throws Exception {
243                     return mWifiManager.isWifiEnabled();
244                 }
245             }, "enabling wifi", connectTimeout);
246 
247         // Wait for some seconds to let wifi to be stable. This increases the chance of success for
248         // subsequent operations.
249         try {
250             Thread.sleep(DEFAULT_WAIT_TIME);
251         } catch (InterruptedException e) {
252             throw new WifiException(String.format("failed to sleep for %d ms", DEFAULT_WAIT_TIME),
253                     e);
254         }
255 
256         removeAllNetworks(false);
257 
258         final int networkId = addNetwork(ssid, psk, scanSsid);
259         if (!mWifiManager.enableNetwork(networkId, true)) {
260             throw new WifiException(String.format("failed to enable network %s", ssid));
261         }
262         if (!mWifiManager.saveConfiguration()) {
263             Log.w(TAG, String.format("failed to save configuration %s", ssid));
264         }
265         connectTimeout = calculateTimeLeft(connectTimeout, timeSpent);
266         timeSpent = waitForCallable(new Callable<Boolean>() {
267                 @Override
268                 public Boolean call() throws Exception {
269                     final SupplicantState state = mWifiManager.getConnectionInfo()
270                             .getSupplicantState();
271                     return SupplicantState.COMPLETED == state;
272                 }
273             }, String.format("associating to network (ssid: %s)", ssid), connectTimeout);
274 
275         connectTimeout = calculateTimeLeft(connectTimeout, timeSpent);
276         timeSpent = waitForCallable(new Callable<Boolean>() {
277                 @Override
278                 public Boolean call() throws Exception {
279                     final WifiInfo info = mWifiManager.getConnectionInfo();
280                     return 0 != info.getIpAddress();
281                 }
282             }, String.format("dhcp assignment (ssid: %s)", ssid), connectTimeout);
283 
284         connectTimeout = calculateTimeLeft(connectTimeout, timeSpent);
285         waitForCallable(new Callable<Boolean>() {
286                 @Override
287                 public Boolean call() throws Exception {
288                     return checkConnectivity(urlToCheck);
289                 }
290             }, String.format("request to %s (ssid: %s)", urlToCheck, ssid), connectTimeout);
291     }
292 
293     /**
294      * Connects a device to a given Wi-Fi network and check connectivity using
295      *
296      * @param ssid SSID of a Wi-Fi network
297      * @param psk PSK of a Wi-Fi network
298      * @param urlToCheck URL to use when checking connectivity
299      * @param connectTimeout duration in seconds to wait for connecting to the network or
300               {@code DEFAULT_TIMEOUT} millis if -1 is passed.
301      * @throws WifiException if the operation fails
302      */
connectToNetwork(final String ssid, final String psk, final String urlToCheck, long connectTimeout)303     public void connectToNetwork(final String ssid, final String psk, final String urlToCheck,
304             long connectTimeout)
305             throws WifiException {
306         connectToNetwork(ssid, psk, urlToCheck, -1, false);
307     }
308 
309     /**
310      * Connects a device to a given Wi-Fi network and check connectivity using
311      * {@code DEFAULT_TIMEOUT}.
312      *
313      * @param ssid SSID of a Wi-Fi network
314      * @param psk PSK of a Wi-Fi network
315      * @param urlToCheck URL to use when checking connectivity
316      * @throws WifiException if the operation fails
317      */
connectToNetwork(final String ssid, final String psk, final String urlToCheck)318     public void connectToNetwork(final String ssid, final String psk, final String urlToCheck)
319             throws WifiException {
320         connectToNetwork(ssid, psk, urlToCheck, -1);
321     }
322 
323     /**
324      * Disconnects a device from Wi-Fi network and disable Wi-Fi.
325      *
326      * @throws WifiException if the operation fails
327      */
disconnectFromNetwork()328     public void disconnectFromNetwork() throws WifiException {
329         if (mWifiManager.isWifiEnabled()) {
330             removeAllNetworks(false);
331         }
332     }
333 
334     /**
335      * Returns Wi-Fi information of a device.
336      *
337      * @return a {@link JSONObject} containing the current Wi-Fi status
338      * @throws WifiException if the operation fails
339      */
getWifiInfo()340     public JSONObject getWifiInfo() throws WifiException {
341         final JSONObject json = new JSONObject();
342 
343         try {
344             final WifiInfo info = mWifiManager.getConnectionInfo();
345             json.put("ssid", info.getSSID());
346             json.put("bssid", info.getBSSID());
347             json.put("hiddenSsid", info.getHiddenSSID());
348             final int addr = info.getIpAddress();
349             // IP address is stored with the first octet in the lowest byte
350             final int a = (addr >> 0) & 0xff;
351             final int b = (addr >> 8) & 0xff;
352             final int c = (addr >> 16) & 0xff;
353             final int d = (addr >> 24) & 0xff;
354             json.put("ipAddress", String.format("%s.%s.%s.%s", a, b, c, d));
355             json.put("linkSpeed", info.getLinkSpeed());
356             json.put("rssi", info.getRssi());
357             json.put("macAddress", info.getMacAddress());
358         } catch (final JSONException e) {
359             throw new WifiException(e.toString());
360         }
361 
362         return json;
363     }
364 
365     /**
366      * Reconnects a device to a last connected Wi-Fi network and check connectivity.
367      *
368      * @param urlToCheck URL to use when checking connectivity
369      * @throws WifiException if the operation fails
370      */
reconnectToLastNetwork(String urlToCheck)371     public void reconnectToLastNetwork(String urlToCheck) throws WifiException {
372         final SharedPreferences prefs = mContext.getSharedPreferences(TAG, 0);
373         final String ssid = prefs.getString("ssid", null);
374         final String psk = prefs.getString("psk", null);
375         final boolean scanSsid = prefs.getBoolean("scan_ssid", false);
376         if (ssid == null) {
377             throw new WifiException("No last connected network.");
378         }
379         connectToNetwork(ssid, psk, urlToCheck, -1, scanSsid);
380     }
381 
updateLastNetwork(final String ssid, final String psk, final boolean scanSsid)382     private void updateLastNetwork(final String ssid, final String psk, final boolean scanSsid) {
383         final SharedPreferences prefs = mContext.getSharedPreferences(TAG, 0);
384         final SharedPreferences.Editor editor = prefs.edit();
385         editor.putString("ssid", ssid);
386         editor.putString("psk", psk);
387         editor.putBoolean("scan_ssid", scanSsid);
388         editor.commit();
389     }
390 
calculateTimeLeft(long connectTimeout, long timeSpent)391     private long calculateTimeLeft(long connectTimeout, long timeSpent) {
392         if (timeSpent > connectTimeout) {
393             return 0;
394         } else {
395             return connectTimeout - timeSpent;
396         }
397     }
398 }
399