1 /*
2  * Copyright (C) 2015 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.compatibility.common.util;
18 
19 import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
20 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
21 
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.net.ProxyInfo;
27 import android.net.Uri;
28 import android.net.wifi.WifiConfiguration;
29 import android.net.wifi.WifiManager;
30 import android.os.Process;
31 import android.text.TextUtils;
32 import android.util.Log;
33 
34 import java.util.List;
35 import java.util.Optional;
36 import java.util.concurrent.CountDownLatch;
37 import java.util.concurrent.TimeUnit;
38 import java.util.stream.Collectors;
39 
40 /**
41  * A simple activity to create and manage wifi configurations.
42  */
43 public class WifiConfigCreator {
44     public static final String ACTION_CREATE_WIFI_CONFIG =
45             "com.android.compatibility.common.util.CREATE_WIFI_CONFIG";
46     public static final String ACTION_UPDATE_WIFI_CONFIG =
47             "com.android.compatibility.common.util.UPDATE_WIFI_CONFIG";
48     public static final String ACTION_REMOVE_WIFI_CONFIG =
49             "com.android.compatibility.common.util.REMOVE_WIFI_CONFIG";
50     public static final String EXTRA_NETID = "extra-netid";
51     public static final String EXTRA_SSID = "extra-ssid";
52     public static final String EXTRA_SECURITY_TYPE = "extra-security-type";
53     public static final String EXTRA_PASSWORD = "extra-password";
54 
55     public static final int SECURITY_TYPE_NONE = 1;
56     public static final int SECURITY_TYPE_WPA = 2;
57     public static final int SECURITY_TYPE_WEP = 3;
58 
59     private static final String TAG = "WifiConfigCreator";
60 
61     private static final long ENABLE_WIFI_WAIT_SEC = 10L;
62 
63     private final Context mContext;
64     private final WifiManager mWifiManager;
65     private WifiManager mCurrentUserWifiManager;
66 
WifiConfigCreator(Context context)67     public WifiConfigCreator(Context context) {
68         this(context, context.getApplicationContext().getSystemService(WifiManager.class));
69     }
70 
WifiConfigCreator(Context context, WifiManager wifiManager)71     public WifiConfigCreator(Context context, WifiManager wifiManager) {
72         mContext = context;
73         mWifiManager = wifiManager;
74         mCurrentUserWifiManager = mContext.getSystemService(WifiManager.class);
75         Log.d(TAG, "WifiConfigCreator: user=" + Process.myUserHandle() + ", ctx=" + context
76                 + ", mgr=" + mWifiManager + ", currentUserMgr=" + mCurrentUserWifiManager);
77     }
78 
79     @Override
toString()80     public String toString() {
81         return "WifiConfigCreator[mWifiManager=" + mWifiManager
82                 + ",mCurrentUserWifiManager=" + mCurrentUserWifiManager + "]";
83     }
84 
85     /**
86      * Adds a new WiFi network.
87      * @return network id or -1 in case of error
88      */
addNetwork(String ssid, boolean hidden, int securityType, String password)89     public int addNetwork(String ssid, boolean hidden, int securityType,
90             String password) throws InterruptedException, SecurityException {
91         checkAndEnableWifi();
92 
93         WifiConfiguration wifiConf = createConfig(ssid, hidden, securityType, password);
94 
95         Log.i(TAG, "Adding SSID " + ssid + " using " + mWifiManager);
96         int netId = mWifiManager.addNetwork(wifiConf);
97 
98         if (netId != -1) {
99             Log.i(TAG, "Added SSID '" + ssid + "': netId = " + netId + "; enabling it");
100             mWifiManager.enableNetwork(netId, true);
101             Log.i(TAG, "SSID '" + ssid + "' enabled!");
102         } else {
103             Log.w(TAG, "Unable to add SSID '" + ssid + "': netId = " + netId);
104         }
105         return netId;
106     }
107 
108     /**
109      * Adds a new wifiConfiguration with OPEN security type, and the given pacProxy
110      * verifies that the proxy is added by getting the configuration back, and checking it.
111      * @return returns the PAC proxy URL after adding the network and getting it from WifiManager
112      * @throws IllegalStateException if any of the WifiManager operations fail
113      */
addHttpProxyNetworkVerifyAndRemove(String ssid, String pacProxyUrl)114     public String addHttpProxyNetworkVerifyAndRemove(String ssid, String pacProxyUrl)
115             throws IllegalStateException {
116         int netId = -1;
117         String retrievedPacProxyUrl;
118 
119         try {
120             netId = addNetworkWithProxy(ssid, pacProxyUrl);
121             WifiConfiguration conf = getWifiConfigurationBySsid(ssid);
122             retrievedPacProxyUrl = getPacProxyUrl(conf);
123 
124             Log.d(TAG, "calling removeNetwork(" + netId + ")");
125             if (!mWifiManager.removeNetwork(netId)) {
126                 throw new IllegalStateException("Failed to remove WifiConfiguration: " + ssid);
127             }
128         } finally {
129             mWifiManager.removeNetwork(netId);
130         }
131         return retrievedPacProxyUrl;
132     }
133 
getPacProxyUrl(WifiConfiguration conf)134     private String getPacProxyUrl(WifiConfiguration conf) {
135         return Optional.of(conf)
136                 .map(WifiConfiguration::getHttpProxy)
137                 .map(ProxyInfo::getPacFileUrl)
138                 .map(Object::toString)
139                 .orElse(null);
140     }
141 
addNetworkWithProxy(String ssid, String pacProxyUrl)142     private int addNetworkWithProxy(String ssid, String pacProxyUrl) {
143         WifiConfiguration conf = createConfig(ssid, false, SECURITY_TYPE_NONE, null);
144 
145         if (pacProxyUrl != null) {
146             conf.setHttpProxy(ProxyInfo.buildPacProxy(Uri.parse(pacProxyUrl)));
147         }
148 
149         Log.d(TAG, "addNetworkWithProxy(ssid=" + ssid + ", pacProxyUrl=" + pacProxyUrl);
150         int netId = mWifiManager.addNetwork(conf);
151         Log.d(TAG, "added: netId=" + netId);
152         if (netId == -1) {
153             throw new IllegalStateException("Failed to addNetwork: " + ssid);
154         }
155         return netId;
156     }
157 
getWifiConfigurationBySsid(String ssid)158     private WifiConfiguration getWifiConfigurationBySsid(String ssid) {
159         WifiConfiguration wifiConfiguration = null;
160         String expectedSsid = wrapInQuotes(ssid);
161         List<WifiConfiguration> configuredNetworks = getConfiguredNetworksWithLogging();
162         for (WifiConfiguration w : configuredNetworks) {
163             if (w.SSID.equals(expectedSsid)) {
164                 wifiConfiguration = w;
165                 break;
166             }
167             Log.v(TAG, "skipping " + w.SSID);
168         }
169         if (wifiConfiguration == null) {
170             throw new IllegalStateException("Failed to get WifiConfiguration for: " + ssid);
171         }
172         return wifiConfiguration;
173     }
174 
175     /**
176      * Updates a new WiFi network.
177      * @return network id (may differ from original) or -1 in case of error
178      */
updateNetwork(WifiConfiguration wifiConf, String ssid, boolean hidden, int securityType, String password)179     public int updateNetwork(WifiConfiguration wifiConf, String ssid, boolean hidden,
180             int securityType, String password) throws InterruptedException, SecurityException {
181         checkAndEnableWifi();
182         if (wifiConf == null) {
183             return -1;
184         }
185 
186         WifiConfiguration conf = createConfig(ssid, hidden, securityType, password);
187         conf.networkId = wifiConf.networkId;
188 
189         int newNetId = mWifiManager.updateNetwork(conf);
190 
191         if (newNetId != -1) {
192             Log.v(TAG, "calling saveConfiguration()");
193             mWifiManager.saveConfiguration();
194             Log.v(TAG, "calling enableNetwork(" + newNetId + ")");
195             mWifiManager.enableNetwork(newNetId, true);
196             Log.v(TAG, "enabled");
197         } else {
198             Log.w(TAG, "Unable to update SSID '" + ssid + "': netId = " + newNetId);
199         }
200         return newNetId;
201     }
202 
203     /**
204      * Updates a new WiFi network.
205      * @return network id (may differ from original) or -1 in case of error
206      */
updateNetwork(int netId, String ssid, boolean hidden, int securityType, String password)207     public int updateNetwork(int netId, String ssid, boolean hidden,
208             int securityType, String password) throws InterruptedException, SecurityException {
209         Log.d(TAG, "updateNetwork(): netId= " + netId + ", ssid=" + ssid + ", hidden=" + hidden);
210         checkAndEnableWifi();
211 
212         WifiConfiguration wifiConf = null;
213         List<WifiConfiguration> configs = getConfiguredNetworksWithLogging();
214         for (WifiConfiguration config : configs) {
215             if (config.networkId == netId) {
216                 wifiConf = config;
217                 break;
218             }
219             Log.v(TAG, "skipping " + config.networkId);
220         }
221         return updateNetwork(wifiConf, ssid, hidden, securityType, password);
222     }
223 
removeNetwork(int netId)224     public boolean removeNetwork(int netId) {
225         Log.v(TAG, "calling removeNetwork(" + netId + ")");
226         boolean removed = mWifiManager.removeNetwork(netId);
227         Log.v(TAG, "removed: " + removed);
228         return removed;
229     }
230 
231     /**
232      * Creates a WifiConfiguration set up according to given parameters
233      * @param ssid SSID of the network
234      * @param hidden Is SSID not broadcast?
235      * @param securityType One of {@link #SECURITY_TYPE_NONE}, {@link #SECURITY_TYPE_WPA} or
236      *                     {@link #SECURITY_TYPE_WEP}
237      * @param password Password for WPA or WEP
238      * @return Created configuration object
239      */
createConfig(String ssid, boolean hidden, int securityType, String password)240     private WifiConfiguration createConfig(String ssid, boolean hidden, int securityType,
241             String password) {
242         WifiConfiguration wifiConf = new WifiConfiguration();
243         if (!TextUtils.isEmpty(ssid)) {
244             wifiConf.SSID = wrapInQuotes(ssid);
245         }
246         wifiConf.status = WifiConfiguration.Status.ENABLED;
247         wifiConf.hiddenSSID = hidden;
248         switch (securityType) {
249             case SECURITY_TYPE_NONE:
250                 wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
251                 break;
252             case SECURITY_TYPE_WPA:
253                 updateForWPAConfiguration(wifiConf, password);
254                 break;
255             case SECURITY_TYPE_WEP:
256                 updateForWEPConfiguration(wifiConf, password);
257                 break;
258         }
259         return wifiConf;
260     }
261 
updateForWPAConfiguration(WifiConfiguration wifiConf, String wifiPassword)262     private void updateForWPAConfiguration(WifiConfiguration wifiConf, String wifiPassword) {
263         wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
264         if (!TextUtils.isEmpty(wifiPassword)) {
265             wifiConf.preSharedKey = wrapInQuotes(wifiPassword);
266         }
267     }
268 
updateForWEPConfiguration(WifiConfiguration wifiConf, String password)269     private void updateForWEPConfiguration(WifiConfiguration wifiConf, String password) {
270         wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
271         wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
272         wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
273         if (!TextUtils.isEmpty(password)) {
274             int length = password.length();
275             if ((length == 10 || length == 26
276                     || length == 58) && password.matches("[0-9A-Fa-f]*")) {
277                 wifiConf.wepKeys[0] = password;
278             } else {
279                 wifiConf.wepKeys[0] = wrapInQuotes(password);
280             }
281             wifiConf.wepTxKeyIndex = 0;
282         }
283     }
284 
checkAndEnableWifi()285     private void checkAndEnableWifi() throws InterruptedException {
286         final CountDownLatch enabledLatch = new CountDownLatch(1);
287 
288         // Register a change receiver first to pick up events between isEnabled and setEnabled
289         final BroadcastReceiver watcher = new BroadcastReceiver() {
290             @Override
291             public void onReceive(Context context, Intent intent) {
292                 Log.d(TAG, "Received intent " + intent.getAction());
293                 if (intent.getIntExtra(EXTRA_WIFI_STATE, -1) == WIFI_STATE_ENABLED) {
294                     enabledLatch.countDown();
295                 }
296             }
297         };
298 
299         mContext.registerReceiver(watcher, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
300         try {
301             // In case wifi is not already enabled, wait for it to come up
302             if (!mWifiManager.isWifiEnabled()) {
303                 Log.v(TAG, "Calling setWifiEnabled(true)");
304                 mWifiManager.setWifiEnabled(true);
305                 Log.v(TAG, "enabled");
306                 enabledLatch.await(ENABLE_WIFI_WAIT_SEC, TimeUnit.SECONDS);
307             }
308         } finally {
309             mContext.unregisterReceiver(watcher);
310         }
311     }
312 
wrapInQuotes(String ssid)313     private String wrapInQuotes(String ssid) {
314         return '"' + ssid + '"';
315     }
316 
getConfiguredNetworksWithLogging()317     private List<WifiConfiguration> getConfiguredNetworksWithLogging() {
318         Log.d(TAG, "calling getConfiguredNetworks() using " + mCurrentUserWifiManager);
319         // Must use a the WifiManager of the current user to list networks, as
320         // getConfiguredNetworks() would return empty on systems using headless system
321         // mode as that method "Return a list of all the networks configured for the current
322         // foreground user", and the system user is running in the background in this case.
323         List<WifiConfiguration> configuredNetworks = mCurrentUserWifiManager
324                 .getConfiguredNetworks();
325         Log.d(TAG, "Got " + configuredNetworks.size() + " networks: "
326                 + configuredNetworks.stream().map((c) -> c.SSID + "/" + c.networkId)
327                         .collect(Collectors.toList()));
328         return configuredNetworks;
329     }
330 }
331 
332