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