diff options
Diffstat (limited to 'wifi/java')
33 files changed, 6161 insertions, 2712 deletions
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 61dfebf..6b08074 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -101,7 +101,9 @@ interface IWifiManager void clearBlacklist(); - Messenger getMessenger(); + Messenger getWifiServiceMessenger(); + + Messenger getWifiStateMachineMessenger(); String getConfigFile(); } diff --git a/wifi/java/android/net/wifi/StateChangeResult.java b/wifi/java/android/net/wifi/StateChangeResult.java index 8ab5982..b15c4a6 100644 --- a/wifi/java/android/net/wifi/StateChangeResult.java +++ b/wifi/java/android/net/wifi/StateChangeResult.java @@ -23,12 +23,15 @@ * @hide */ public class StateChangeResult { - StateChangeResult(int networkId, String BSSID, SupplicantState state) { + StateChangeResult(int networkId, String SSID, String BSSID, SupplicantState state) { this.state = state; + this.SSID = SSID; this.BSSID = BSSID; this.networkId = networkId; } + int networkId; + String SSID; String BSSID; SupplicantState state; } diff --git a/wifi/java/android/net/wifi/SupplicantState.java b/wifi/java/android/net/wifi/SupplicantState.java index 509b02c..4a2037d 100644 --- a/wifi/java/android/net/wifi/SupplicantState.java +++ b/wifi/java/android/net/wifi/SupplicantState.java @@ -171,8 +171,8 @@ public enum SupplicantState implements Parcelable { } - /* Supplicant associating or authenticating is considered a handshake state */ - static boolean isHandshakeState(SupplicantState state) { + /** Supplicant associating or authenticating is considered a handshake state {@hide} */ + public static boolean isHandshakeState(SupplicantState state) { switch(state) { case AUTHENTICATING: case ASSOCIATING: diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java index cbd284c..6aeac5f 100644 --- a/wifi/java/android/net/wifi/SupplicantStateTracker.java +++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java @@ -39,6 +39,7 @@ class SupplicantStateTracker extends StateMachine { private static final boolean DBG = false; private WifiStateMachine mWifiStateMachine; + private WifiConfigStore mWifiConfigStore; private int mAuthenticationFailuresCount = 0; /* Indicates authentication failure in supplicant broadcast. * TODO: enhance auth failure reporting to include notification @@ -62,11 +63,12 @@ class SupplicantStateTracker extends StateMachine { private State mCompletedState = new CompletedState(); private State mDormantState = new DormantState(); - public SupplicantStateTracker(Context context, WifiStateMachine wsm, Handler target) { - super(TAG, target.getLooper()); + public SupplicantStateTracker(Context c, WifiStateMachine wsm, WifiConfigStore wcs, Handler t) { + super(TAG, t.getLooper()); - mContext = context; + mContext = c; mWifiStateMachine = wsm; + mWifiConfigStore = wcs; addState(mDefaultState); addState(mUninitializedState, mDefaultState); addState(mInactiveState, mDefaultState); @@ -85,11 +87,11 @@ class SupplicantStateTracker extends StateMachine { private void handleNetworkConnectionFailure(int netId) { /* If other networks disabled during connection, enable them */ if (mNetworksDisabledDuringConnect) { - WifiConfigStore.enableAllNetworks(); + mWifiConfigStore.enableAllNetworks(); mNetworksDisabledDuringConnect = false; } /* Disable failed network */ - WifiConfigStore.disableNetwork(netId, WifiConfiguration.DISABLED_AUTH_FAILURE); + mWifiConfigStore.disableNetwork(netId, WifiConfiguration.DISABLED_AUTH_FAILURE); } private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) { @@ -173,7 +175,7 @@ class SupplicantStateTracker extends StateMachine { case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE: transitionTo(mUninitializedState); break; - case WifiStateMachine.CMD_CONNECT_NETWORK: + case WifiManager.CONNECT_NETWORK: mNetworksDisabledDuringConnect = true; break; default: @@ -285,7 +287,7 @@ class SupplicantStateTracker extends StateMachine { /* Reset authentication failure count */ mAuthenticationFailuresCount = 0; if (mNetworksDisabledDuringConnect) { - WifiConfigStore.enableAllNetworks(); + mWifiConfigStore.enableAllNetworks(); mNetworksDisabledDuringConnect = false; } } diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index 26f7199..3c761c8 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -22,8 +22,10 @@ import android.net.DhcpInfoInternal; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkUtils; +import android.net.NetworkInfo.DetailedState; import android.net.ProxyProperties; import android.net.RouteInfo; +import android.net.wifi.WifiConfiguration.EnterpriseField; import android.net.wifi.WifiConfiguration.IpAssignment; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiConfiguration.ProxySettings; @@ -31,6 +33,9 @@ import android.net.wifi.WifiConfiguration.Status; import android.net.wifi.NetworkUpdateResult; import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; import android.os.Environment; +import android.os.Message; +import android.os.Handler; +import android.os.HandlerThread; import android.text.TextUtils; import android.util.Log; @@ -50,6 +55,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; /** * This class provides the API to manage configured @@ -98,12 +104,12 @@ import java.util.List; */ class WifiConfigStore { - private static Context sContext; + private Context mContext; private static final String TAG = "WifiConfigStore"; private static final boolean DBG = false; /* configured networks with network id as the key */ - private static HashMap<Integer, WifiConfiguration> sConfiguredNetworks = + private HashMap<Integer, WifiConfiguration> mConfiguredNetworks = new HashMap<Integer, WifiConfiguration>(); /* A network id is a unique identifier for a network configured in the @@ -113,11 +119,11 @@ class WifiConfigStore { * that is generated from SSID and security type of the network. A mapping * from the generated unique id to network id of the network is needed to * map supplicant config to IP configuration. */ - private static HashMap<Integer, Integer> sNetworkIds = + private HashMap<Integer, Integer> mNetworkIds = new HashMap<Integer, Integer>(); /* Tracks the highest priority of configured networks */ - private static int sLastPriority = -1; + private int mLastPriority = -1; private static final String ipConfigFile = Environment.getDataDirectory() + "/misc/wifi/ipconfig.txt"; @@ -136,13 +142,19 @@ class WifiConfigStore { private static final String EXCLUSION_LIST_KEY = "exclusionList"; private static final String EOS = "eos"; + private WifiNative mWifiNative; + + WifiConfigStore(Context c, WifiNative wn) { + mContext = c; + mWifiNative = wn; + } + /** - * Initialize context, fetch the list of configured networks + * Fetch the list of configured networks * and enable all stored networks in supplicant. */ - static void initialize(Context context) { + void initialize() { if (DBG) log("Loading config and enabling all networks"); - sContext = context; loadConfiguredNetworks(); enableAllNetworks(); } @@ -151,12 +163,10 @@ class WifiConfigStore { * Fetch the list of currently configured networks * @return List of networks */ - static List<WifiConfiguration> getConfiguredNetworks() { + List<WifiConfiguration> getConfiguredNetworks() { List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>(); - synchronized (sConfiguredNetworks) { - for(WifiConfiguration config : sConfiguredNetworks.values()) { - networks.add(new WifiConfiguration(config)); - } + for(WifiConfiguration config : mConfiguredNetworks.values()) { + networks.add(new WifiConfiguration(config)); } return networks; } @@ -165,23 +175,21 @@ class WifiConfigStore { * enable all networks and save config. This will be a no-op if the list * of configured networks indicates all networks as being enabled */ - static void enableAllNetworks() { + void enableAllNetworks() { boolean networkEnabledStateChanged = false; - synchronized (sConfiguredNetworks) { - for(WifiConfiguration config : sConfiguredNetworks.values()) { - if(config != null && config.status == Status.DISABLED) { - if(WifiNative.enableNetworkCommand(config.networkId, false)) { - networkEnabledStateChanged = true; - config.status = Status.ENABLED; - } else { - loge("Enable network failed on " + config.networkId); - } + for(WifiConfiguration config : mConfiguredNetworks.values()) { + if(config != null && config.status == Status.DISABLED) { + if(mWifiNative.enableNetwork(config.networkId, false)) { + networkEnabledStateChanged = true; + config.status = Status.ENABLED; + } else { + loge("Enable network failed on " + config.networkId); } } } if (networkEnabledStateChanged) { - WifiNative.saveConfigCommand(); + mWifiNative.saveConfig(); sendConfiguredNetworksChangedBroadcast(); } } @@ -198,7 +206,7 @@ class WifiConfigStore { * @param config The configuration details in WifiConfiguration * @return the networkId now associated with the specified configuration */ - static int selectNetwork(WifiConfiguration config) { + int selectNetwork(WifiConfiguration config) { if (config != null) { NetworkUpdateResult result = addOrUpdateNetworkNative(config); int netId = result.getNetworkId(); @@ -223,27 +231,25 @@ class WifiConfigStore { * * @param netId network to select for connection */ - static void selectNetwork(int netId) { + void selectNetwork(int netId) { // Reset the priority of each network at start or if it goes too high. - if (sLastPriority == -1 || sLastPriority > 1000000) { - synchronized (sConfiguredNetworks) { - for(WifiConfiguration config : sConfiguredNetworks.values()) { - if (config.networkId != INVALID_NETWORK_ID) { - config.priority = 0; - addOrUpdateNetworkNative(config); - } + if (mLastPriority == -1 || mLastPriority > 1000000) { + for(WifiConfiguration config : mConfiguredNetworks.values()) { + if (config.networkId != INVALID_NETWORK_ID) { + config.priority = 0; + addOrUpdateNetworkNative(config); } } - sLastPriority = 0; + mLastPriority = 0; } // Set to the highest priority and save the configuration. WifiConfiguration config = new WifiConfiguration(); config.networkId = netId; - config.priority = ++sLastPriority; + config.priority = ++mLastPriority; addOrUpdateNetworkNative(config); - WifiNative.saveConfigCommand(); + mWifiNative.saveConfig(); /* Enable the given network while disabling all other networks */ enableNetworkWithoutBroadcast(netId, true); @@ -256,46 +262,67 @@ class WifiConfigStore { * Add/update the specified configuration and save config * * @param config WifiConfiguration to be saved + * @return network update result */ - static NetworkUpdateResult saveNetwork(WifiConfiguration config) { + NetworkUpdateResult saveNetwork(WifiConfiguration config) { boolean newNetwork = (config.networkId == INVALID_NETWORK_ID); NetworkUpdateResult result = addOrUpdateNetworkNative(config); int netId = result.getNetworkId(); /* enable a new network */ if (newNetwork && netId != INVALID_NETWORK_ID) { - WifiNative.enableNetworkCommand(netId, false); - synchronized (sConfiguredNetworks) { - sConfiguredNetworks.get(netId).status = Status.ENABLED; - } + mWifiNative.enableNetwork(netId, false); + mConfiguredNetworks.get(netId).status = Status.ENABLED; } - WifiNative.saveConfigCommand(); + mWifiNative.saveConfig(); sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ? WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE); return result; } + void updateStatus(int netId, DetailedState state) { + if (netId != INVALID_NETWORK_ID) { + WifiConfiguration config = mConfiguredNetworks.get(netId); + if (config == null) return; + switch (state) { + case CONNECTED: + config.status = Status.CURRENT; + break; + case DISCONNECTED: + //If network is already disabled, keep the status + if (config.status == Status.CURRENT) { + config.status = Status.ENABLED; + } + break; + default: + //do nothing, retain the existing state + break; + } + } + } + /** * Forget the specified network and save config * * @param netId network to forget + * @return {@code true} if it succeeds, {@code false} otherwise */ - static void forgetNetwork(int netId) { - if (WifiNative.removeNetworkCommand(netId)) { - WifiNative.saveConfigCommand(); + boolean forgetNetwork(int netId) { + if (mWifiNative.removeNetwork(netId)) { + mWifiNative.saveConfig(); WifiConfiguration target = null; - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - if (config != null) { - target = sConfiguredNetworks.remove(netId); - sNetworkIds.remove(configKey(config)); - } + WifiConfiguration config = mConfiguredNetworks.get(netId); + if (config != null) { + target = mConfiguredNetworks.remove(netId); + mNetworkIds.remove(configKey(config)); } if (target != null) { writeIpAndProxyConfigurations(); sendConfiguredNetworksChangedBroadcast(target, WifiManager.CHANGE_REASON_REMOVED); } + return true; } else { loge("Failed to remove network " + netId); + return false; } } @@ -306,11 +333,12 @@ class WifiConfigStore { * state machine * * @param config wifi configuration to add/update + * @return network Id */ - static int addOrUpdateNetwork(WifiConfiguration config) { + int addOrUpdateNetwork(WifiConfiguration config) { NetworkUpdateResult result = addOrUpdateNetworkNative(config); if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) { - sendConfiguredNetworksChangedBroadcast(sConfiguredNetworks.get(result.getNetworkId()), + sendConfiguredNetworksChangedBroadcast(mConfiguredNetworks.get(result.getNetworkId()), result.isNewNetwork ? WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE); } @@ -324,17 +352,16 @@ class WifiConfigStore { * state machine for network removal * * @param netId network to be removed + * @return {@code true} if it succeeds, {@code false} otherwise */ - static boolean removeNetwork(int netId) { - boolean ret = WifiNative.removeNetworkCommand(netId); + boolean removeNetwork(int netId) { + boolean ret = mWifiNative.removeNetwork(netId); WifiConfiguration config = null; - synchronized (sConfiguredNetworks) { - if (ret) { - config = sConfiguredNetworks.get(netId); - if (config != null) { - config = sConfiguredNetworks.remove(netId); - sNetworkIds.remove(configKey(config)); - } + if (ret) { + config = mConfiguredNetworks.get(netId); + if (config != null) { + config = mConfiguredNetworks.remove(netId); + mNetworkIds.remove(configKey(config)); } } if (config != null) { @@ -350,15 +377,16 @@ class WifiConfigStore { * state machine for connecting to a network * * @param netId network to be enabled + * @return {@code true} if it succeeds, {@code false} otherwise */ - static boolean enableNetwork(int netId, boolean disableOthers) { + boolean enableNetwork(int netId, boolean disableOthers) { boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers); if (disableOthers) { sendConfiguredNetworksChangedBroadcast(); } else { WifiConfiguration enabledNetwork = null; - synchronized(sConfiguredNetworks) { - enabledNetwork = sConfiguredNetworks.get(netId); + synchronized(mConfiguredNetworks) { + enabledNetwork = mConfiguredNetworks.get(netId); } // check just in case the network was removed by someone else. if (enabledNetwork != null) { @@ -369,13 +397,11 @@ class WifiConfigStore { return ret; } - static boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) { - boolean ret = WifiNative.enableNetworkCommand(netId, disableOthers); + boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) { + boolean ret = mWifiNative.enableNetwork(netId, disableOthers); - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - if (config != null) config.status = Status.ENABLED; - } + WifiConfiguration config = mConfiguredNetworks.get(netId); + if (config != null) config.status = Status.ENABLED; if (disableOthers) { markAllNetworksDisabledExcept(netId); @@ -386,8 +412,9 @@ class WifiConfigStore { /** * Disable a network. Note that there is no saveConfig operation. * @param netId network to be disabled + * @return {@code true} if it succeeds, {@code false} otherwise */ - static boolean disableNetwork(int netId) { + boolean disableNetwork(int netId) { return disableNetwork(netId, WifiConfiguration.DISABLED_UNKNOWN_REASON); } @@ -395,18 +422,17 @@ class WifiConfigStore { * Disable a network. Note that there is no saveConfig operation. * @param netId network to be disabled * @param reason reason code network was disabled + * @return {@code true} if it succeeds, {@code false} otherwise */ - static boolean disableNetwork(int netId, int reason) { - boolean ret = WifiNative.disableNetworkCommand(netId); + boolean disableNetwork(int netId, int reason) { + boolean ret = mWifiNative.disableNetwork(netId); WifiConfiguration network = null; - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - /* Only change the reason if the network was not previously disabled */ - if (config != null && config.status != Status.DISABLED) { - config.status = Status.DISABLED; - config.disableReason = reason; - network = config; - } + WifiConfiguration config = mConfiguredNetworks.get(netId); + /* Only change the reason if the network was not previously disabled */ + if (config != null && config.status != Status.DISABLED) { + config.status = Status.DISABLED; + config.disableReason = reason; + network = config; } if (network != null) { sendConfiguredNetworksChangedBroadcast(network, @@ -417,18 +443,21 @@ class WifiConfigStore { /** * Save the configured networks in supplicant to disk + * @return {@code true} if it succeeds, {@code false} otherwise */ - static boolean saveConfig() { - return WifiNative.saveConfigCommand(); + boolean saveConfig() { + return mWifiNative.saveConfig(); } /** * Start WPS pin method configuration with pin obtained * from the access point + * @param config WPS configuration + * @return Wps result containing status and pin */ - static WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) { + WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) { WpsResult result = new WpsResult(); - if (WifiNative.startWpsWithPinFromAccessPointCommand(config.BSSID, config.pin)) { + if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) { /* WPS leaves all networks disabled */ markAllNetworksDisabled(); result.status = WpsResult.Status.SUCCESS; @@ -444,9 +473,9 @@ class WifiConfigStore { * from the device * @return WpsResult indicating status and pin */ - static WpsResult startWpsWithPinFromDevice(WpsInfo config) { + WpsResult startWpsWithPinFromDevice(WpsInfo config) { WpsResult result = new WpsResult(); - result.pin = WifiNative.startWpsWithPinFromDeviceCommand(config.BSSID); + result.pin = mWifiNative.startWpsPinDisplay(config.BSSID); /* WPS leaves all networks disabled */ if (!TextUtils.isEmpty(result.pin)) { markAllNetworksDisabled(); @@ -460,10 +489,12 @@ class WifiConfigStore { /** * Start WPS push button configuration + * @param config WPS configuration + * @return WpsResult indicating status and pin */ - static WpsResult startWpsPbc(WpsInfo config) { + WpsResult startWpsPbc(WpsInfo config) { WpsResult result = new WpsResult(); - if (WifiNative.startWpsPbcCommand(config.BSSID)) { + if (mWifiNative.startWpsPbc(config.BSSID)) { /* WPS leaves all networks disabled */ markAllNetworksDisabled(); result.status = WpsResult.Status.SUCCESS; @@ -476,12 +507,11 @@ class WifiConfigStore { /** * Fetch the link properties for a given network id + * @return LinkProperties for the given network id */ - static LinkProperties getLinkProperties(int netId) { - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - if (config != null) return new LinkProperties(config.linkProperties); - } + LinkProperties getLinkProperties(int netId) { + WifiConfiguration config = mConfiguredNetworks.get(netId); + if (config != null) return new LinkProperties(config.linkProperties); return null; } @@ -491,8 +521,9 @@ class WifiConfigStore { * right now until NetworkUtils is fixed. When we do * that, we should remove handling DhcpInfo and move * to using LinkProperties + * @return DhcpInfoInternal for the given network id */ - static DhcpInfoInternal getIpConfiguration(int netId) { + DhcpInfoInternal getIpConfiguration(int netId) { DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal(); LinkProperties linkProperties = getLinkProperties(netId); @@ -518,41 +549,40 @@ class WifiConfigStore { /** * set IP configuration for a given network id */ - static void setIpConfiguration(int netId, DhcpInfoInternal dhcpInfo) { + void setIpConfiguration(int netId, DhcpInfoInternal dhcpInfo) { LinkProperties linkProperties = dhcpInfo.makeLinkProperties(); - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - if (config != null) { - // add old proxy details - if(config.linkProperties != null) { - linkProperties.setHttpProxy(config.linkProperties.getHttpProxy()); - } - config.linkProperties = linkProperties; + WifiConfiguration config = mConfiguredNetworks.get(netId); + if (config != null) { + // add old proxy details + if(config.linkProperties != null) { + linkProperties.setHttpProxy(config.linkProperties.getHttpProxy()); } + config.linkProperties = linkProperties; } } /** * clear IP configuration for a given network id + * @param network id */ - static void clearIpConfiguration(int netId) { - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - if (config != null && config.linkProperties != null) { - // Clear everything except proxy - ProxyProperties proxy = config.linkProperties.getHttpProxy(); - config.linkProperties.clear(); - config.linkProperties.setHttpProxy(proxy); - } + void clearIpConfiguration(int netId) { + WifiConfiguration config = mConfiguredNetworks.get(netId); + if (config != null && config.linkProperties != null) { + // Clear everything except proxy + ProxyProperties proxy = config.linkProperties.getHttpProxy(); + config.linkProperties.clear(); + config.linkProperties.setHttpProxy(proxy); } } /** * Fetch the proxy properties for a given network id + * @param network id + * @return ProxyProperties for the network id */ - static ProxyProperties getProxyProperties(int netId) { + ProxyProperties getProxyProperties(int netId) { LinkProperties linkProperties = getLinkProperties(netId); if (linkProperties != null) { return new ProxyProperties(linkProperties.getHttpProxy()); @@ -562,13 +592,13 @@ class WifiConfigStore { /** * Return if the specified network is using static IP + * @param network id + * @return {@code true} if using static ip for netId */ - static boolean isUsingStaticIp(int netId) { - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - if (config != null && config.ipAssignment == IpAssignment.STATIC) { - return true; - } + boolean isUsingStaticIp(int netId) { + WifiConfiguration config = mConfiguredNetworks.get(netId); + if (config != null && config.ipAssignment == IpAssignment.STATIC) { + return true; } return false; } @@ -579,111 +609,133 @@ class WifiConfigStore { * @param reason The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED, * WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE. */ - private static void sendConfiguredNetworksChangedBroadcast(WifiConfiguration network, + private void sendConfiguredNetworksChangedBroadcast(WifiConfiguration network, int reason) { Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false); intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network); intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason); - sContext.sendBroadcast(intent); + mContext.sendBroadcast(intent); } /** * Should be called when multiple network configuration changes are made. */ - private static void sendConfiguredNetworksChangedBroadcast() { + private void sendConfiguredNetworksChangedBroadcast() { Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true); - sContext.sendBroadcast(intent); + mContext.sendBroadcast(intent); } - static void loadConfiguredNetworks() { - String listStr = WifiNative.listNetworksCommand(); - sLastPriority = 0; + void loadConfiguredNetworks() { + String listStr = mWifiNative.listNetworks(); + mLastPriority = 0; - synchronized (sConfiguredNetworks) { - sConfiguredNetworks.clear(); - sNetworkIds.clear(); + mConfiguredNetworks.clear(); + mNetworkIds.clear(); - if (listStr == null) - return; + if (listStr == null) + return; - String[] lines = listStr.split("\n"); - // Skip the first line, which is a header - for (int i = 1; i < lines.length; i++) { - String[] result = lines[i].split("\t"); - // network-id | ssid | bssid | flags - WifiConfiguration config = new WifiConfiguration(); - try { - config.networkId = Integer.parseInt(result[0]); - } catch(NumberFormatException e) { - continue; - } - if (result.length > 3) { - if (result[3].indexOf("[CURRENT]") != -1) - config.status = WifiConfiguration.Status.CURRENT; - else if (result[3].indexOf("[DISABLED]") != -1) - config.status = WifiConfiguration.Status.DISABLED; - else - config.status = WifiConfiguration.Status.ENABLED; - } else { + String[] lines = listStr.split("\n"); + // Skip the first line, which is a header + for (int i = 1; i < lines.length; i++) { + String[] result = lines[i].split("\t"); + // network-id | ssid | bssid | flags + WifiConfiguration config = new WifiConfiguration(); + try { + config.networkId = Integer.parseInt(result[0]); + } catch(NumberFormatException e) { + continue; + } + if (result.length > 3) { + if (result[3].indexOf("[CURRENT]") != -1) + config.status = WifiConfiguration.Status.CURRENT; + else if (result[3].indexOf("[DISABLED]") != -1) + config.status = WifiConfiguration.Status.DISABLED; + else config.status = WifiConfiguration.Status.ENABLED; - } - readNetworkVariables(config); - if (config.priority > sLastPriority) { - sLastPriority = config.priority; - } - sConfiguredNetworks.put(config.networkId, config); - sNetworkIds.put(configKey(config), config.networkId); + } else { + config.status = WifiConfiguration.Status.ENABLED; } + readNetworkVariables(config); + if (config.priority > mLastPriority) { + mLastPriority = config.priority; + } + mConfiguredNetworks.put(config.networkId, config); + mNetworkIds.put(configKey(config), config.networkId); } + readIpAndProxyConfigurations(); sendConfiguredNetworksChangedBroadcast(); } - static void updateIpAndProxyFromWpsConfig(int netId, WpsInfo wpsConfig) { - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get(netId); - if (config != null) { - config.ipAssignment = wpsConfig.ipAssignment; - config.proxySettings = wpsConfig.proxySettings; - config.linkProperties = wpsConfig.linkProperties; - writeIpAndProxyConfigurations(); - } - } - } - /* Mark all networks except specified netId as disabled */ - private static void markAllNetworksDisabledExcept(int netId) { - synchronized (sConfiguredNetworks) { - for(WifiConfiguration config : sConfiguredNetworks.values()) { - if(config != null && config.networkId != netId) { - if (config.status != Status.DISABLED) { - config.status = Status.DISABLED; - config.disableReason = WifiConfiguration.DISABLED_UNKNOWN_REASON; - } + private void markAllNetworksDisabledExcept(int netId) { + for(WifiConfiguration config : mConfiguredNetworks.values()) { + if(config != null && config.networkId != netId) { + if (config.status != Status.DISABLED) { + config.status = Status.DISABLED; + config.disableReason = WifiConfiguration.DISABLED_UNKNOWN_REASON; } } } } - private static void markAllNetworksDisabled() { + private void markAllNetworksDisabled() { markAllNetworksDisabledExcept(INVALID_NETWORK_ID); } - private static void writeIpAndProxyConfigurations() { + private void writeIpAndProxyConfigurations() { - DataOutputStream out = null; - try { - out = new DataOutputStream(new BufferedOutputStream( - new FileOutputStream(ipConfigFile))); + /* Make a copy */ + List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>(); + for(WifiConfiguration config : mConfiguredNetworks.values()) { + networks.add(new WifiConfiguration(config)); + } + + DelayedDiskWrite.write(networks); + } + + private static class DelayedDiskWrite { + + private static HandlerThread sDiskWriteHandlerThread; + private static Handler sDiskWriteHandler; + /* Tracks multiple writes on the same thread */ + private static int sWriteSequence = 0; + private static final String TAG = "DelayedDiskWrite"; + + static void write (final List<WifiConfiguration> networks) { + + /* Do a delayed write to disk on a seperate handler thread */ + synchronized (DelayedDiskWrite.class) { + if (++sWriteSequence == 1) { + sDiskWriteHandlerThread = new HandlerThread("WifiConfigThread"); + sDiskWriteHandlerThread.start(); + sDiskWriteHandler = new Handler(sDiskWriteHandlerThread.getLooper()); + } + } + + sDiskWriteHandler.post(new Runnable() { + @Override + public void run() { + onWriteCalled(networks); + } + }); + } - out.writeInt(IPCONFIG_FILE_VERSION); + private static void onWriteCalled(List<WifiConfiguration> networks) { - synchronized (sConfiguredNetworks) { - for(WifiConfiguration config : sConfiguredNetworks.values()) { + DataOutputStream out = null; + try { + out = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(ipConfigFile))); + + out.writeInt(IPCONFIG_FILE_VERSION); + + for(WifiConfiguration config : networks) { boolean writeToFile = false; try { @@ -756,7 +808,7 @@ class WifiConfigStore { /* Ignore */ break; default: - loge("Ignore invalid proxy settings while writing"); + loge("Ignthisore invalid proxy settings while writing"); break; } if (writeToFile) { @@ -768,20 +820,33 @@ class WifiConfigStore { } out.writeUTF(EOS); } - } - } catch (IOException e) { - loge("Error writing data file"); - } finally { - if (out != null) { - try { - out.close(); - } catch (Exception e) {} + } catch (IOException e) { + loge("Error writing data file"); + } finally { + if (out != null) { + try { + out.close(); + } catch (Exception e) {} + } + + //Quit if no more writes sent + synchronized (DelayedDiskWrite.class) { + if (--sWriteSequence == 0) { + sDiskWriteHandler.getLooper().quit(); + sDiskWriteHandler = null; + sDiskWriteHandlerThread = null; + } + } } } + + private static void loge(String s) { + Log.e(TAG, s); + } } - private static void readIpAndProxyConfigurations() { + private void readIpAndProxyConfigurations() { DataInputStream in = null; try { @@ -854,48 +919,46 @@ class WifiConfigStore { } while (true); if (id != -1) { - synchronized (sConfiguredNetworks) { - WifiConfiguration config = sConfiguredNetworks.get( - sNetworkIds.get(id)); + WifiConfiguration config = mConfiguredNetworks.get( + mNetworkIds.get(id)); - if (config == null) { - loge("configuration found for missing network, ignored"); - } else { - config.linkProperties = linkProperties; - switch (ipAssignment) { - case STATIC: - case DHCP: - config.ipAssignment = ipAssignment; - break; - case UNASSIGNED: - //Ignore - break; - default: - loge("Ignore invalid ip assignment while reading"); - break; - } + if (config == null) { + loge("configuration found for missing network, ignored"); + } else { + config.linkProperties = linkProperties; + switch (ipAssignment) { + case STATIC: + case DHCP: + config.ipAssignment = ipAssignment; + break; + case UNASSIGNED: + //Ignore + break; + default: + loge("Ignore invalid ip assignment while reading"); + break; + } - switch (proxySettings) { - case STATIC: - config.proxySettings = proxySettings; - ProxyProperties proxyProperties = - new ProxyProperties(proxyHost, proxyPort, exclusionList); - linkProperties.setHttpProxy(proxyProperties); - break; - case NONE: - config.proxySettings = proxySettings; - break; - case UNASSIGNED: - //Ignore - break; - default: - loge("Ignore invalid proxy settings while reading"); - break; - } + switch (proxySettings) { + case STATIC: + config.proxySettings = proxySettings; + ProxyProperties proxyProperties = + new ProxyProperties(proxyHost, proxyPort, exclusionList); + linkProperties.setHttpProxy(proxyProperties); + break; + case NONE: + config.proxySettings = proxySettings; + break; + case UNASSIGNED: + //Ignore + break; + default: + loge("Ignore invalid proxy settings while reading"); + break; } } } else { - loge("Missing id while parsing configuration"); + if (DBG) log("Missing id while parsing configuration"); } } } catch (EOFException ignore) { @@ -910,7 +973,7 @@ class WifiConfigStore { } } - private static NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config) { + private NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config) { /* * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty * network configuration. Otherwise, the networkId should @@ -920,12 +983,12 @@ class WifiConfigStore { boolean newNetwork = false; // networkId of INVALID_NETWORK_ID means we want to create a new network if (netId == INVALID_NETWORK_ID) { - Integer savedNetId = sNetworkIds.get(configKey(config)); + Integer savedNetId = mNetworkIds.get(configKey(config)); if (savedNetId != null) { netId = savedNetId; } else { newNetwork = true; - netId = WifiNative.addNetworkCommand(); + netId = mWifiNative.addNetwork(); if (netId < 0) { loge("Failed to add a network!"); return new NetworkUpdateResult(INVALID_NETWORK_ID); @@ -938,7 +1001,7 @@ class WifiConfigStore { setVariables: { if (config.SSID != null && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.ssidVarName, config.SSID)) { @@ -947,7 +1010,7 @@ class WifiConfigStore { } if (config.BSSID != null && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.bssidVarName, config.BSSID)) { @@ -958,7 +1021,7 @@ class WifiConfigStore { String allowedKeyManagementString = makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings); if (config.allowedKeyManagement.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.KeyMgmt.varName, allowedKeyManagementString)) { @@ -970,7 +1033,7 @@ class WifiConfigStore { String allowedProtocolsString = makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings); if (config.allowedProtocols.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.Protocol.varName, allowedProtocolsString)) { @@ -982,7 +1045,7 @@ class WifiConfigStore { String allowedAuthAlgorithmsString = makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings); if (config.allowedAuthAlgorithms.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.AuthAlgorithm.varName, allowedAuthAlgorithmsString)) { @@ -995,7 +1058,7 @@ class WifiConfigStore { makeString(config.allowedPairwiseCiphers, WifiConfiguration.PairwiseCipher.strings); if (config.allowedPairwiseCiphers.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.PairwiseCipher.varName, allowedPairwiseCiphersString)) { @@ -1007,7 +1070,7 @@ class WifiConfigStore { String allowedGroupCiphersString = makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings); if (config.allowedGroupCiphers.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.GroupCipher.varName, allowedGroupCiphersString)) { @@ -1019,7 +1082,7 @@ class WifiConfigStore { // Prevent client screw-up by passing in a WifiConfiguration we gave it // by preventing "*" as a key. if (config.preSharedKey != null && !config.preSharedKey.equals("*") && - !WifiNative.setNetworkVariableCommand( + !mWifiNative.setNetworkVariable( netId, WifiConfiguration.pskVarName, config.preSharedKey)) { @@ -1033,7 +1096,7 @@ class WifiConfigStore { // Prevent client screw-up by passing in a WifiConfiguration we gave it // by preventing "*" as a key. if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) { - if (!WifiNative.setNetworkVariableCommand( + if (!mWifiNative.setNetworkVariable( netId, WifiConfiguration.wepKeyVarNames[i], config.wepKeys[i])) { @@ -1046,7 +1109,7 @@ class WifiConfigStore { } if (hasSetKey) { - if (!WifiNative.setNetworkVariableCommand( + if (!mWifiNative.setNetworkVariable( netId, WifiConfiguration.wepTxKeyIdxVarName, Integer.toString(config.wepTxKeyIndex))) { @@ -1055,7 +1118,7 @@ class WifiConfigStore { } } - if (!WifiNative.setNetworkVariableCommand( + if (!mWifiNative.setNetworkVariable( netId, WifiConfiguration.priorityVarName, Integer.toString(config.priority))) { @@ -1064,7 +1127,7 @@ class WifiConfigStore { break setVariables; } - if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand( + if (config.hiddenSSID && !mWifiNative.setNetworkVariable( netId, WifiConfiguration.hiddenSSIDVarName, Integer.toString(config.hiddenSSID ? 1 : 0))) { @@ -1078,10 +1141,18 @@ class WifiConfigStore { String varName = field.varName(); String value = field.value(); if (value != null) { - if (field != config.eap) { + if (field == config.engine) { + /* + * If the field is declared as an integer, it must not + * be null + */ + if (value.length() == 0) { + value = "0"; + } + } else if (field != config.eap) { value = (value.length() == 0) ? "NULL" : convertToQuotedString(value); } - if (!WifiNative.setNetworkVariableCommand( + if (!mWifiNative.setNetworkVariable( netId, varName, value)) { @@ -1096,43 +1167,38 @@ class WifiConfigStore { if (updateFailed) { if (newNetwork) { - WifiNative.removeNetworkCommand(netId); + mWifiNative.removeNetwork(netId); loge("Failed to set a network variable, removed network: " + netId); } return new NetworkUpdateResult(INVALID_NETWORK_ID); } /* An update of the network variables requires reading them - * back from the supplicant to update sConfiguredNetworks. + * back from the supplicant to update mConfiguredNetworks. * This is because some of the variables (SSID, wep keys & * passphrases) reflect different values when read back than * when written. For example, wep key is stored as * irrespective * of the value sent to the supplicant */ - WifiConfiguration sConfig; - synchronized (sConfiguredNetworks) { - sConfig = sConfiguredNetworks.get(netId); - } - if (sConfig == null) { - sConfig = new WifiConfiguration(); - sConfig.networkId = netId; + WifiConfiguration currentConfig = mConfiguredNetworks.get(netId); + if (currentConfig == null) { + currentConfig = new WifiConfiguration(); + currentConfig.networkId = netId; } - readNetworkVariables(sConfig); + readNetworkVariables(currentConfig); - synchronized (sConfiguredNetworks) { - sConfiguredNetworks.put(netId, sConfig); - sNetworkIds.put(configKey(sConfig), netId); - } + mConfiguredNetworks.put(netId, currentConfig); + mNetworkIds.put(configKey(currentConfig), netId); - NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(sConfig, config); + NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config); result.setIsNewNetwork(newNetwork); result.setNetworkId(netId); return result; } /* Compare current and new configuration and write to file on change */ - private static NetworkUpdateResult writeIpAndProxyConfigurationsOnChange( + private NetworkUpdateResult writeIpAndProxyConfigurationsOnChange( WifiConfiguration currentConfig, WifiConfiguration newConfig) { boolean ipChanged = false; @@ -1232,7 +1298,7 @@ class WifiConfigStore { return new NetworkUpdateResult(ipChanged, proxyChanged); } - private static void addIpSettingsFromConfig(LinkProperties linkProperties, + private void addIpSettingsFromConfig(LinkProperties linkProperties, WifiConfiguration config) { for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) { linkProperties.addLinkAddress(linkAddr); @@ -1251,7 +1317,7 @@ class WifiConfigStore { * * @param config the {@link WifiConfiguration} object to be filled in. */ - private static void readNetworkVariables(WifiConfiguration config) { + private void readNetworkVariables(WifiConfiguration config) { int netId = config.networkId; if (netId < 0) @@ -1264,21 +1330,21 @@ class WifiConfigStore { */ String value; - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName); + value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName); if (!TextUtils.isEmpty(value)) { config.SSID = value; } else { config.SSID = null; } - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName); + value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.bssidVarName); if (!TextUtils.isEmpty(value)) { config.BSSID = value; } else { config.BSSID = null; } - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName); + value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.priorityVarName); config.priority = -1; if (!TextUtils.isEmpty(value)) { try { @@ -1287,7 +1353,7 @@ class WifiConfigStore { } } - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName); + value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName); config.hiddenSSID = false; if (!TextUtils.isEmpty(value)) { try { @@ -1296,7 +1362,7 @@ class WifiConfigStore { } } - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName); + value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName); config.wepTxKeyIndex = -1; if (!TextUtils.isEmpty(value)) { try { @@ -1306,7 +1372,7 @@ class WifiConfigStore { } for (int i = 0; i < 4; i++) { - value = WifiNative.getNetworkVariableCommand(netId, + value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepKeyVarNames[i]); if (!TextUtils.isEmpty(value)) { config.wepKeys[i] = value; @@ -1315,14 +1381,14 @@ class WifiConfigStore { } } - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName); + value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pskVarName); if (!TextUtils.isEmpty(value)) { config.preSharedKey = value; } else { config.preSharedKey = null; } - value = WifiNative.getNetworkVariableCommand(config.networkId, + value = mWifiNative.getNetworkVariable(config.networkId, WifiConfiguration.Protocol.varName); if (!TextUtils.isEmpty(value)) { String vals[] = value.split(" "); @@ -1335,7 +1401,7 @@ class WifiConfigStore { } } - value = WifiNative.getNetworkVariableCommand(config.networkId, + value = mWifiNative.getNetworkVariable(config.networkId, WifiConfiguration.KeyMgmt.varName); if (!TextUtils.isEmpty(value)) { String vals[] = value.split(" "); @@ -1348,7 +1414,7 @@ class WifiConfigStore { } } - value = WifiNative.getNetworkVariableCommand(config.networkId, + value = mWifiNative.getNetworkVariable(config.networkId, WifiConfiguration.AuthAlgorithm.varName); if (!TextUtils.isEmpty(value)) { String vals[] = value.split(" "); @@ -1361,7 +1427,7 @@ class WifiConfigStore { } } - value = WifiNative.getNetworkVariableCommand(config.networkId, + value = mWifiNative.getNetworkVariable(config.networkId, WifiConfiguration.PairwiseCipher.varName); if (!TextUtils.isEmpty(value)) { String vals[] = value.split(" "); @@ -1374,7 +1440,7 @@ class WifiConfigStore { } } - value = WifiNative.getNetworkVariableCommand(config.networkId, + value = mWifiNative.getNetworkVariable(config.networkId, WifiConfiguration.GroupCipher.varName); if (!TextUtils.isEmpty(value)) { String vals[] = value.split(" "); @@ -1389,25 +1455,83 @@ class WifiConfigStore { for (WifiConfiguration.EnterpriseField field : config.enterpriseFields) { - value = WifiNative.getNetworkVariableCommand(netId, + value = mWifiNative.getNetworkVariable(netId, field.varName()); if (!TextUtils.isEmpty(value)) { - if (field != config.eap) value = removeDoubleQuotes(value); + if (field != config.eap && field != config.engine) { + value = removeDoubleQuotes(value); + } field.setValue(value); } } + + migrateOldEapTlsIfNecessary(config, netId); + } + + /** + * Migration code for old EAP-TLS configurations. This should only be used + * when restoring an old wpa_supplicant.conf or upgrading from a previous + * platform version. + * + * @param config the configuration to be migrated + * @param netId the wpa_supplicant's net ID + * @param value the old private_key value + */ + private void migrateOldEapTlsIfNecessary(WifiConfiguration config, int netId) { + String value = mWifiNative.getNetworkVariable(netId, + WifiConfiguration.OLD_PRIVATE_KEY_NAME); + /* + * If the old configuration value is not present, then there is nothing + * to do. + */ + if (TextUtils.isEmpty(value)) { + return; + } else { + // Also ignore it if it's empty quotes. + value = removeDoubleQuotes(value); + if (TextUtils.isEmpty(value)) { + return; + } + } + + config.engine.setValue(WifiConfiguration.ENGINE_ENABLE); + config.engine_id.setValue(convertToQuotedString(WifiConfiguration.KEYSTORE_ENGINE_ID)); + + /* + * The old key started with the keystore:// URI prefix, but we don't + * need that anymore. Trim it off if it exists. + */ + final String keyName; + if (value.startsWith(WifiConfiguration.KEYSTORE_URI)) { + keyName = new String(value.substring(WifiConfiguration.KEYSTORE_URI.length())); + } else { + keyName = value; + } + config.key_id.setValue(convertToQuotedString(keyName)); + + // Now tell the wpa_supplicant the new configuration values. + final EnterpriseField needsUpdate[] = { config.engine, config.engine_id, config.key_id }; + for (EnterpriseField field : needsUpdate) { + mWifiNative.setNetworkVariable(netId, field.varName(), field.value()); + } + + // Remove old private_key string so we don't run this again. + mWifiNative.setNetworkVariable(netId, WifiConfiguration.OLD_PRIVATE_KEY_NAME, + convertToQuotedString("")); + + saveConfig(); } - private static String removeDoubleQuotes(String string) { + private String removeDoubleQuotes(String string) { if (string.length() <= 2) return ""; return string.substring(1, string.length() - 1); } - private static String convertToQuotedString(String string) { + private String convertToQuotedString(String string) { return "\"" + string + "\""; } - private static String makeString(BitSet set, String[] strings) { + private String makeString(BitSet set, String[] strings) { StringBuffer buf = new StringBuffer(); int nextSetBit = -1; @@ -1427,7 +1551,7 @@ class WifiConfigStore { return buf.toString(); } - private static int lookupString(String string, String[] strings) { + private int lookupString(String string, String[] strings) { int size = strings.length; string = string.replace('-', '_'); @@ -1462,10 +1586,10 @@ class WifiConfigStore { return key.hashCode(); } - static String dump() { + String dump() { StringBuffer sb = new StringBuffer(); String LS = System.getProperty("line.separator"); - sb.append("sLastPriority ").append(sLastPriority).append(LS); + sb.append("mLastPriority ").append(mLastPriority).append(LS); sb.append("Configured networks ").append(LS); for (WifiConfiguration conf : getConfiguredNetworks()) { sb.append(conf).append(LS); @@ -1473,15 +1597,15 @@ class WifiConfigStore { return sb.toString(); } - public static String getConfigFile() { + public String getConfigFile() { return ipConfigFile; } - private static void loge(String s) { + private void loge(String s) { Log.e(TAG, s); } - private static void log(String s) { + private void log(String s) { Log.d(TAG, s); } } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 85a6f27..dfc1b18 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -29,6 +29,33 @@ import java.util.BitSet; */ public class WifiConfiguration implements Parcelable { + /** + * In old configurations, the "private_key" field was used. However, newer + * configurations use the key_id field with the engine_id set to "keystore". + * If this field is found in the configuration, the migration code is + * triggered. + * @hide + */ + public static final String OLD_PRIVATE_KEY_NAME = "private_key"; + + /** + * String representing the keystore OpenSSL ENGINE's ID. + * @hide + */ + public static final String KEYSTORE_ENGINE_ID = "keystore"; + + /** + * String representing the keystore URI used for wpa_supplicant. + * @hide + */ + public static final String KEYSTORE_URI = "keystore://"; + + /** + * String to set the engine value to when it should be enabled. + * @hide + */ + public static final String ENGINE_ENABLE = "1"; + /** {@hide} */ public static final String ssidVarName = "ssid"; /** {@hide} */ @@ -82,14 +109,18 @@ public class WifiConfiguration implements Parcelable { /** {@hide} */ public EnterpriseField client_cert = new EnterpriseField("client_cert"); /** {@hide} */ - public EnterpriseField private_key = new EnterpriseField("private_key"); + public EnterpriseField engine = new EnterpriseField("engine"); + /** {@hide} */ + public EnterpriseField engine_id = new EnterpriseField("engine_id"); + /** {@hide} */ + public EnterpriseField key_id = new EnterpriseField("key_id"); /** {@hide} */ public EnterpriseField ca_cert = new EnterpriseField("ca_cert"); /** {@hide} */ public EnterpriseField[] enterpriseFields = { eap, phase2, identity, anonymous_identity, password, client_cert, - private_key, ca_cert }; + engine, engine_id, key_id, ca_cert }; /** * Recognized key management schemes. diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index d5b404e..7bb927b 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -70,7 +70,6 @@ public class WifiInfo implements Parcelable { private InetAddress mIpAddress; private String mMacAddress; - private boolean mExplicitConnect; WifiInfo() { mSSID = null; @@ -80,7 +79,6 @@ public class WifiInfo implements Parcelable { mRssi = -9999; mLinkSpeed = -1; mHiddenSSID = false; - mExplicitConnect = false; } /** @@ -98,7 +96,6 @@ public class WifiInfo implements Parcelable { mLinkSpeed = source.mLinkSpeed; mIpAddress = source.mIpAddress; mMacAddress = source.mMacAddress; - mExplicitConnect = source.mExplicitConnect; } } @@ -175,22 +172,6 @@ public class WifiInfo implements Parcelable { mNetworkId = id; } - - /** - * @hide - */ - public boolean isExplicitConnect() { - return mExplicitConnect; - } - - /** - * @hide - */ - public void setExplicitConnect(boolean explicitConnect) { - this.mExplicitConnect = explicitConnect; - } - - /** * Each configured network has a unique small integer ID, used to identify * the network when performing operations on the supplicant. This method @@ -279,8 +260,7 @@ public class WifiInfo implements Parcelable { append(mSupplicantState == null ? none : mSupplicantState). append(", RSSI: ").append(mRssi). append(", Link speed: ").append(mLinkSpeed). - append(", Net ID: ").append(mNetworkId). - append(", Explicit connect: ").append(mExplicitConnect); + append(", Net ID: ").append(mNetworkId); return sb.toString(); } @@ -304,7 +284,6 @@ public class WifiInfo implements Parcelable { dest.writeString(getSSID()); dest.writeString(mBSSID); dest.writeString(mMacAddress); - dest.writeByte(mExplicitConnect ? (byte)1 : (byte)0); mSupplicantState.writeToParcel(dest, flags); } @@ -324,7 +303,6 @@ public class WifiInfo implements Parcelable { info.setSSID(in.readString()); info.mBSSID = in.readString(); info.mMacAddress = in.readString(); - info.mExplicitConnect = in.readByte() == 1 ? true : false; info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in); return info; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 0e19c4c..8aa613b 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -23,11 +23,15 @@ import android.net.DhcpInfo; import android.os.Binder; import android.os.IBinder; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.RemoteException; import android.os.WorkSource; import android.os.Messenger; +import android.util.SparseArray; import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; import java.util.List; @@ -289,24 +293,6 @@ public class WifiManager { public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError"; /** - * Broadcast intent action for reporting errors - * @hide - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ERROR_ACTION = "android.net.wifi.ERROR"; - /** - * The type of error being reported - * @hide - */ - public static final String EXTRA_ERROR_CODE = "errorCode"; - - /** - * Valid error codes - * @hide - */ - public static final int WPS_OVERLAP_ERROR = 1; - - /** * Broadcast intent action indicating that the configured networks changed. * This can be as a result of adding/updating/deleting a network. If * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set to true the new configuration @@ -454,6 +440,13 @@ public class WifiManager { private static final int MAX_RSSI = -55; /** + * Number of RSSI levels used in the framework to initiate + * {@link #RSSI_CHANGED_ACTION} broadcast + * @hide + */ + public static final int RSSI_LEVELS = 5; + + /** * Auto settings in the driver. The driver could choose to operate on both * 2.4 GHz and 5 GHz or make a dynamic decision on selecting the band. * @hide @@ -500,9 +493,6 @@ public class WifiManager { /* Number of currently active WifiLocks and MulticastLocks */ private int mActiveLockCount; - /* For communication with WifiService */ - private AsyncChannel mAsyncChannel = new AsyncChannel(); - /** * Create a new WifiManager instance. * Applications will almost always want to use @@ -656,17 +646,6 @@ public class WifiManager { } /** - * Disable a configured network asynchronously. This call is for abnormal network - * events, and the user may be notified of network change, if they recently attempted - * to connect to the specified network. - * @param netId the ID of the network as returned by {@link #addNetwork}. - * @hide - */ - public void disableNetwork(int netId, int reason) { - mAsyncChannel.sendMessage(CMD_DISABLE_NETWORK, netId, reason); - } - - /** * Disassociate from the currently active access point. This may result * in the asynchronous delivery of state change events. * @return {@code true} if the operation succeeded @@ -1101,37 +1080,258 @@ public class WifiManager { /* TODO: deprecate synchronous API and open up the following API */ + private static final int BASE = Protocol.BASE_WIFI_MANAGER; + /* Commands to WifiService */ /** @hide */ - public static final int CMD_CONNECT_NETWORK = 1; + public static final int CONNECT_NETWORK = BASE + 1; + /** @hide */ + public static final int CONNECT_NETWORK_FAILED = BASE + 2; + /** @hide */ + public static final int CONNECT_NETWORK_SUCCEEDED = BASE + 3; + + /** @hide */ + public static final int FORGET_NETWORK = BASE + 4; + /** @hide */ + public static final int FORGET_NETWORK_FAILED = BASE + 5; + /** @hide */ + public static final int FORGET_NETWORK_SUCCEEDED = BASE + 6; + + /** @hide */ + public static final int SAVE_NETWORK = BASE + 7; + /** @hide */ + public static final int SAVE_NETWORK_FAILED = BASE + 8; + /** @hide */ + public static final int SAVE_NETWORK_SUCCEEDED = BASE + 9; + /** @hide */ - public static final int CMD_FORGET_NETWORK = 2; + public static final int START_WPS = BASE + 10; /** @hide */ - public static final int CMD_SAVE_NETWORK = 3; + public static final int START_WPS_SUCCEEDED = BASE + 11; /** @hide */ - public static final int CMD_START_WPS = 4; + public static final int WPS_FAILED = BASE + 12; /** @hide */ - public static final int CMD_DISABLE_NETWORK = 5; + public static final int WPS_COMPLETED = BASE + 13; - /* Events from WifiService */ /** @hide */ - public static final int CMD_WPS_COMPLETED = 11; + public static final int CANCEL_WPS = BASE + 14; + /** @hide */ + public static final int CANCEL_WPS_FAILED = BASE + 15; + /** @hide */ + public static final int CANCEL_WPS_SUCCEDED = BASE + 16; + + /** @hide */ + public static final int DISABLE_NETWORK = BASE + 17; + /** @hide */ + public static final int DISABLE_NETWORK_FAILED = BASE + 18; + /** @hide */ + public static final int DISABLE_NETWORK_SUCCEEDED = BASE + 19; /* For system use only */ /** @hide */ - public static final int CMD_ENABLE_TRAFFIC_STATS_POLL = 21; + public static final int ENABLE_TRAFFIC_STATS_POLL = BASE + 21; /** @hide */ - public static final int CMD_TRAFFIC_STATS_POLL = 22; + public static final int TRAFFIC_STATS_POLL = BASE + 22; + + + /** + * Passed with {@link ActionListener#onFailure}. + * Indicates that the operation failed due to an internal error. + * @hide + */ + public static final int ERROR = 0; + + /** + * Passed with {@link ActionListener#onFailure}. + * Indicates that the operation is already in progress + * @hide + */ + public static final int IN_PROGRESS = 1; + + /** + * Passed with {@link ActionListener#onFailure}. + * Indicates that the operation failed because the framework is busy and + * unable to service the request + * @hide + */ + public static final int BUSY = 2; + + /* WPS specific errors */ + /** WPS overlap detected {@hide} */ + public static final int WPS_OVERLAP_ERROR = 3; + /** WEP on WPS is prohibited {@hide} */ + public static final int WPS_WEP_PROHIBITED = 4; + /** TKIP only prohibited {@hide} */ + public static final int WPS_TKIP_ONLY_PROHIBITED = 5; + /** Authentication failure on WPS {@hide} */ + public static final int WPS_AUTH_FAILURE = 6; + /** WPS timed out {@hide} */ + public static final int WPS_TIMED_OUT = 7; + + /** Interface for callback invocation when framework channel is lost {@hide} */ + public interface ChannelListener { + /** + * The channel to the framework has been disconnected. + * Application could try re-initializing using {@link #initialize} + */ + public void onChannelDisconnected(); + } + + /** Interface for callback invocation on an application action {@hide} */ + public interface ActionListener { + /** The operation succeeded */ + public void onSuccess(); + /** + * The operation failed + * @param reason The reason for failure could be one of + * {@link #ERROR}, {@link #IN_PROGRESS} or {@link #BUSY} + */ + public void onFailure(int reason); + } + + /** Interface for callback invocation on a start WPS action {@hide} */ + public interface WpsListener { + /** WPS start succeeded */ + public void onStartSuccess(String pin); + + /** WPS operation completed succesfully */ + public void onCompletion(); + + /** + * WPS operation failed + * @param reason The reason for failure could be one of + * {@link #IN_PROGRESS}, {@link #WPS_OVERLAP_ERROR},{@link #ERROR} or {@link #BUSY} + */ + public void onFailure(int reason); + } /** - * Initiate an asynchronous channel connection setup + * A channel that connects the application to the Wifi framework. + * Most operations require a Channel as an argument. An instance of Channel is obtained + * by doing a call on {@link #initialize} + * @hide + */ + public static class Channel { + Channel(Looper looper, ChannelListener l) { + mAsyncChannel = new AsyncChannel(); + mHandler = new WifiHandler(looper); + mChannelListener = l; + } + private ChannelListener mChannelListener; + private SparseArray<Object> mListenerMap = new SparseArray<Object>(); + private Object mListenerMapLock = new Object(); + private int mListenerKey = 0; + private static final int INVALID_KEY = -1; + + AsyncChannel mAsyncChannel; + WifiHandler mHandler; + class WifiHandler extends Handler { + WifiHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + Object listener = removeListener(message.arg2); + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (mChannelListener != null) { + mChannelListener.onChannelDisconnected(); + mChannelListener = null; + } + break; + /* ActionListeners grouped together */ + case WifiManager.CONNECT_NETWORK_FAILED: + case WifiManager.FORGET_NETWORK_FAILED: + case WifiManager.SAVE_NETWORK_FAILED: + case WifiManager.CANCEL_WPS_FAILED: + case WifiManager.DISABLE_NETWORK_FAILED: + if (listener != null) { + ((ActionListener) listener).onFailure(message.arg1); + } + break; + /* ActionListeners grouped together */ + case WifiManager.CONNECT_NETWORK_SUCCEEDED: + case WifiManager.FORGET_NETWORK_SUCCEEDED: + case WifiManager.SAVE_NETWORK_SUCCEEDED: + case WifiManager.CANCEL_WPS_SUCCEDED: + case WifiManager.DISABLE_NETWORK_SUCCEEDED: + if (listener != null) { + ((ActionListener) listener).onSuccess(); + } + break; + case WifiManager.START_WPS_SUCCEEDED: + if (listener != null) { + WpsResult result = (WpsResult) message.obj; + ((WpsListener) listener).onStartSuccess(result.pin); + //Listener needs to stay until completion or failure + synchronized(mListenerMapLock) { + mListenerMap.put(message.arg2, listener); + } + } + break; + case WifiManager.WPS_COMPLETED: + if (listener != null) { + ((WpsListener) listener).onCompletion(); + } + break; + case WifiManager.WPS_FAILED: + if (listener != null) { + ((WpsListener) listener).onFailure(message.arg1); + } + break; + default: + //ignore + break; + } + } + } + + int putListener(Object listener) { + if (listener == null) return INVALID_KEY; + int key; + synchronized (mListenerMapLock) { + do { + key = mListenerKey++; + } while (key == INVALID_KEY); + mListenerMap.put(key, listener); + } + return key; + } + + Object removeListener(int key) { + if (key == INVALID_KEY) return null; + synchronized (mListenerMapLock) { + Object listener = mListenerMap.get(key); + mListenerMap.remove(key); + return listener; + } + } + } + + /** + * Registers the application with the Wi-Fi framework. This function + * must be the first to be called before any Wi-Fi operations are performed. + * * @param srcContext is the context of the source - * @param srcHandler is the handler on which the source receives messages + * @param srcLooper is the Looper on which the callbacks are receivied + * @param listener for callback at loss of framework communication. Can be null. + * @return Channel instance that is necessary for performing any further Wi-Fi operations. + * A null is returned upon failure to initialize. * @hide */ - public void asyncConnect(Context srcContext, Handler srcHandler) { - mAsyncChannel.connect(srcContext, srcHandler, getMessenger()); - } + public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { + Messenger messenger = getWifiServiceMessenger(); + if (messenger == null) return null; + + Channel c = new Channel(srcLooper, listener); + if (c.mAsyncChannel.connectSync(srcContext, c.mHandler, messenger) + == AsyncChannel.STATUS_SUCCESSFUL) { + return c; + } else { + return null; + } + } /** * Connect to a network with the given configuration. The network also @@ -1141,15 +1341,20 @@ public class WifiManager { * sequence of addNetwork(), enableNetwork(), saveConfiguration() and * reconnect() * + * @param c is the channel created at {@link #initialize} * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. + * @param listener for callbacks on success or failure. Can be null. * @hide */ - public void connectNetwork(WifiConfiguration config) { - if (config == null) { - return; - } - mAsyncChannel.sendMessage(CMD_CONNECT_NETWORK, config); + public void connect(Channel c, WifiConfiguration config, ActionListener listener) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (config == null) throw new IllegalArgumentException("config cannot be null"); + + // Use INVALID_NETWORK_ID for arg1 when passing a config object + // arg1 is used to pass network id when the network already exists + c.mAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID, + c.putListener(listener), config); } /** @@ -1158,15 +1363,17 @@ public class WifiManager { * This function is used instead of a enableNetwork(), saveConfiguration() and * reconnect() * + * @param c is the channel created at {@link #initialize} * @param networkId the network id identifiying the network in the * supplicant configuration list + * @param listener for callbacks on success or failure. Can be null. * @hide */ - public void connectNetwork(int networkId) { - if (networkId < 0) { - return; - } - mAsyncChannel.sendMessage(CMD_CONNECT_NETWORK, networkId); + public void connect(Channel c, int networkId, ActionListener listener) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative"); + + c.mAsyncChannel.sendMessage(CONNECT_NETWORK, networkId, c.putListener(listener)); } /** @@ -1180,16 +1387,17 @@ public class WifiManager { * For an existing network, it accomplishes the task of updateNetwork() * and saveConfiguration() * + * @param c is the channel created at {@link #initialize} * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. + * @param listener for callbacks on success or failure. Can be null. * @hide */ - public void saveNetwork(WifiConfiguration config) { - if (config == null) { - return; - } + public void save(Channel c, WifiConfiguration config, ActionListener listener) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (config == null) throw new IllegalArgumentException("config cannot be null"); - mAsyncChannel.sendMessage(CMD_SAVE_NETWORK, config); + c.mAsyncChannel.sendMessage(SAVE_NETWORK, 0, c.putListener(listener), config); } /** @@ -1198,47 +1406,94 @@ public class WifiManager { * This function is used instead of a sequence of removeNetwork() * and saveConfiguration(). * + * @param c is the channel created at {@link #initialize} * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. + * @param listener for callbacks on success or failure. Can be null. * @hide */ - public void forgetNetwork(int netId) { - if (netId < 0) { - return; - } + public void forget(Channel c, int netId, ActionListener listener) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative"); - mAsyncChannel.sendMessage(CMD_FORGET_NETWORK, netId); + c.mAsyncChannel.sendMessage(FORGET_NETWORK, netId, c.putListener(listener)); + } + + /** + * Disable network + * + * @param c is the channel created at {@link #initialize} + * @param netId is the network Id + * @param listener for callbacks on success or failure. Can be null. + * @hide + */ + public void disable(Channel c, int netId, ActionListener listener) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative"); + + c.mAsyncChannel.sendMessage(DISABLE_NETWORK, netId, c.putListener(listener)); } /** * Start Wi-fi Protected Setup * + * @param c is the channel created at {@link #initialize} * @param config WPS configuration + * @param listener for callbacks on success or failure. Can be null. * @hide */ - public void startWps(WpsInfo config) { - if (config == null) { - return; - } + public void startWps(Channel c, WpsInfo config, WpsListener listener) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (config == null) throw new IllegalArgumentException("config cannot be null"); - mAsyncChannel.sendMessage(CMD_START_WPS, config); + c.mAsyncChannel.sendMessage(START_WPS, 0, c.putListener(listener), config); } /** + * Cancel any ongoing Wi-fi Protected Setup + * + * @param c is the channel created at {@link #initialize} + * @param listener for callbacks on success or failure. Can be null. + * @hide + */ + public void cancelWps(Channel c, ActionListener listener) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + + c.mAsyncChannel.sendMessage(CANCEL_WPS, 0, c.putListener(listener)); + } + + + + /** * Get a reference to WifiService handler. This is used by a client to establish * an AsyncChannel communication with WifiService * * @return Messenger pointing to the WifiService handler * @hide */ - public Messenger getMessenger() { + public Messenger getWifiServiceMessenger() { + try { + return mService.getWifiServiceMessenger(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Get a reference to WifiStateMachine handler. + * @return Messenger pointing to the WifiService handler + * @hide + */ + public Messenger getWifiStateMachineMessenger() { try { - return mService.getMessenger(); + return mService.getWifiStateMachineMessenger(); } catch (RemoteException e) { return null; } } + + /** * Returns the file in which IP and proxy configuration data is stored * @hide diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java index 2ccc8a2..a447c86 100644 --- a/wifi/java/android/net/wifi/WifiMonitor.java +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -20,6 +20,8 @@ import android.net.NetworkInfo; import android.net.wifi.p2p.WifiP2pConfig; import android.net.wifi.p2p.WifiP2pDevice; import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pProvDiscEvent; +import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; import android.net.wifi.StateChangeResult; import android.os.Message; import android.util.Log; @@ -28,6 +30,7 @@ import android.util.Log; import com.android.internal.util.Protocol; import com.android.internal.util.StateMachine; +import java.util.List; import java.util.regex.Pattern; import java.util.regex.Matcher; @@ -63,7 +66,23 @@ public class WifiMonitor { "pre-shared key may be incorrect"; /* WPS events */ + private static final String WPS_SUCCESS_STR = "WPS-SUCCESS"; + + /* Format: WPS-FAIL msg=%d [config_error=%d] [reason=%d (%s)] */ + private static final String WPS_FAIL_STR = "WPS-FAIL"; + private static final String WPS_FAIL_PATTERN = + "WPS-FAIL msg=\\d+(?: config_error=(\\d+))?(?: reason=(\\d+))?"; + + /* config error code values for config_error=%d */ + private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12; + private static final int CONFIG_AUTH_FAILURE = 18; + + /* reason code values for reason=%d */ + private static final int REASON_TKIP_ONLY_PROHIBITED = 1; + private static final int REASON_WEP_PROHIBITED = 2; + private static final String WPS_OVERLAP_STR = "WPS-OVERLAP-DETECTED"; + private static final String WPS_TIMEOUT_STR = "WPS-TIMEOUT"; /** * Names of events from wpa_supplicant (minus the prefix). In the @@ -149,6 +168,9 @@ public class WifiMonitor { /* P2P-DEVICE-LOST p2p_dev_addr=42:fc:89:e1:e2:27 */ private static final String P2P_DEVICE_LOST_STR = "P2P-DEVICE-LOST"; + /* P2P-FIND-STOPPED */ + private static final String P2P_FIND_STOPPED_STR = "P2P-FIND-STOPPED"; + /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */ private static final String P2P_GO_NEG_REQUEST_STR = "P2P-GO-NEG-REQUEST"; @@ -181,6 +203,10 @@ public class WifiMonitor { pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 group_capab=0x0 */ private static final String P2P_PROV_DISC_PBC_REQ_STR = "P2P-PROV-DISC-PBC-REQ"; + + /* P2P-PROV-DISC-PBC-RESP 02:12:47:f2:5a:36 */ + private static final String P2P_PROV_DISC_PBC_RSP_STR = "P2P-PROV-DISC-PBC-RESP"; + /* P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 group_capab=0x0 */ @@ -190,13 +216,60 @@ public class WifiMonitor { group_capab=0x0 */ private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN"; + /* + * Protocol format is as follows.<br> + * See the Table.62 in the WiFi Direct specification for the detail. + * ______________________________________________________________ + * | Length(2byte) | Type(1byte) | TransId(1byte)}| + * ______________________________________________________________ + * | status(1byte) | vendor specific(variable) | + * + * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300000101 + * length=3, service type=0(ALL Service), transaction id=1, + * status=1(service protocol type not available)<br> + * + * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300020201 + * length=3, service type=2(UPnP), transaction id=2, + * status=1(service protocol type not available) + * + * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 990002030010757569643a3131323 + * 2646534652d383537342d353961622d393332322d3333333435363738393034343a3 + * a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f6e746 + * 56e744469726563746f72793a322c757569643a36383539646564652d383537342d3 + * 53961622d393333322d3132333435363738393031323a3a75706e703a726f6f74646 + * 576696365 + * length=153,type=2(UPnP),transaction id=3,status=0 + * + * UPnP Protocol format is as follows. + * ______________________________________________________ + * | Version (1) | USN (Variable) | + * + * version=0x10(UPnP1.0) data=usn:uuid:1122de4e-8574-59ab-9322-33345678 + * 9044::urn:schemas-upnp-org:service:ContentDirectory:2,usn:uuid:6859d + * ede-8574-59ab-9332-123456789012::upnp:rootdevice + * + * P2P-SERV-DISC-RESP 58:17:0c:bc:dd:ca 21 1900010200045f6970 + * 70c00c000c01094d795072696e746572c027 + * length=25, type=1(Bonjour),transaction id=2,status=0 + * + * Bonjour Protocol format is as follows. + * __________________________________________________________ + * |DNS Name(Variable)|DNS Type(1)|Version(1)|RDATA(Variable)| + * + * DNS Name=_ipp._tcp.local.,DNS type=12(PTR), Version=1, + * RDATA=MyPrinter._ipp._tcp.local. + * + */ + private static final String P2P_SERV_DISC_RESP_STR = "P2P-SERV-DISC-RESP"; + private static final String HOST_AP_EVENT_PREFIX_STR = "AP"; - /* AP-STA-CONNECTED 42:fc:89:a8:96:09 */ + /* AP-STA-CONNECTED 42:fc:89:a8:96:09 dev_addr=02:90:4c:a0:92:54 */ private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED"; /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 */ private static final String AP_STA_DISCONNECTED_STR = "AP-STA-DISCONNECTED"; private final StateMachine mStateMachine; + private final WifiNative mWifiNative; /* Supplicant events reported to a state machine */ private static final int BASE = Protocol.BASE_WIFI_MONITOR; @@ -215,10 +288,16 @@ public class WifiMonitor { public static final int SUPPLICANT_STATE_CHANGE_EVENT = BASE + 6; /* Password failure and EAP authentication failure */ public static final int AUTHENTICATION_FAILURE_EVENT = BASE + 7; - /* WPS overlap detected */ - public static final int WPS_OVERLAP_EVENT = BASE + 8; + /* WPS success detected */ + public static final int WPS_SUCCESS_EVENT = BASE + 8; + /* WPS failure detected */ + public static final int WPS_FAIL_EVENT = BASE + 9; + /* WPS overlap detected */ + public static final int WPS_OVERLAP_EVENT = BASE + 10; + /* WPS timeout detected */ + public static final int WPS_TIMEOUT_EVENT = BASE + 11; /* Driver was hung */ - public static final int DRIVER_HUNG_EVENT = BASE + 9; + public static final int DRIVER_HUNG_EVENT = BASE + 12; /* P2P events */ public static final int P2P_DEVICE_FOUND_EVENT = BASE + 21; @@ -233,8 +312,11 @@ public class WifiMonitor { public static final int P2P_INVITATION_RECEIVED_EVENT = BASE + 31; public static final int P2P_INVITATION_RESULT_EVENT = BASE + 32; public static final int P2P_PROV_DISC_PBC_REQ_EVENT = BASE + 33; - public static final int P2P_PROV_DISC_ENTER_PIN_EVENT = BASE + 34; - public static final int P2P_PROV_DISC_SHOW_PIN_EVENT = BASE + 35; + public static final int P2P_PROV_DISC_PBC_RSP_EVENT = BASE + 34; + public static final int P2P_PROV_DISC_ENTER_PIN_EVENT = BASE + 35; + public static final int P2P_PROV_DISC_SHOW_PIN_EVENT = BASE + 36; + public static final int P2P_FIND_STOPPED_EVENT = BASE + 37; + public static final int P2P_SERV_DISC_RESP_EVENT = BASE + 38; /* hostap events */ public static final int AP_STA_DISCONNECTED_EVENT = BASE + 41; @@ -260,8 +342,9 @@ public class WifiMonitor { */ private static final int MAX_RECV_ERRORS = 10; - public WifiMonitor(StateMachine wifiStateMachine) { + public WifiMonitor(StateMachine wifiStateMachine, WifiNative wifiNative) { mStateMachine = wifiStateMachine; + mWifiNative = wifiNative; } public void startMonitoring() { @@ -286,7 +369,7 @@ public class WifiMonitor { //noinspection InfiniteLoopStatement for (;;) { - String eventStr = WifiNative.waitForEvent(); + String eventStr = mWifiNative.waitForEvent(); // Skip logging the common but mostly uninteresting scan-results event if (false && eventStr.indexOf(SCAN_RESULTS_STR) == -1) { @@ -296,8 +379,14 @@ public class WifiMonitor { if (eventStr.startsWith(WPA_EVENT_PREFIX_STR) && 0 < eventStr.indexOf(PASSWORD_MAY_BE_INCORRECT_STR)) { mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT); + } else if (eventStr.startsWith(WPS_SUCCESS_STR)) { + mStateMachine.sendMessage(WPS_SUCCESS_EVENT); + } else if (eventStr.startsWith(WPS_FAIL_STR)) { + handleWpsFailEvent(eventStr); } else if (eventStr.startsWith(WPS_OVERLAP_STR)) { mStateMachine.sendMessage(WPS_OVERLAP_EVENT); + } else if (eventStr.startsWith(WPS_TIMEOUT_STR)) { + mStateMachine.sendMessage(WPS_TIMEOUT_EVENT); } else if (eventStr.startsWith(P2P_EVENT_PREFIX_STR)) { handleP2pEvents(eventStr); } else if (eventStr.startsWith(HOST_AP_EVENT_PREFIX_STR)) { @@ -358,17 +447,6 @@ public class WifiMonitor { handleDriverEvent(eventData); } else if (event == TERMINATING) { /** - * If monitor socket is closed, we have already - * stopped the supplicant, simply exit the monitor thread - */ - if (eventData.startsWith(MONITOR_SOCKET_CLOSED_STR)) { - if (false) { - Log.d(TAG, "Monitor socket is closed, exiting thread"); - } - break; - } - - /** * Close the supplicant connection if we see * too many recv errors */ @@ -400,7 +478,7 @@ public class WifiMonitor { int connectTries = 0; while (true) { - if (WifiNative.connectToSupplicant()) { + if (mWifiNative.connectToSupplicant()) { return true; } if (connectTries++ < 5) { @@ -446,6 +524,43 @@ public class WifiMonitor { } } + private void handleWpsFailEvent(String dataString) { + final Pattern p = Pattern.compile(WPS_FAIL_PATTERN); + Matcher match = p.matcher(dataString); + if (match.find()) { + String cfgErr = match.group(1); + String reason = match.group(2); + + if (reason != null) { + switch(Integer.parseInt(reason)) { + case REASON_TKIP_ONLY_PROHIBITED: + mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT, + WifiManager.WPS_TKIP_ONLY_PROHIBITED, 0)); + return; + case REASON_WEP_PROHIBITED: + mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT, + WifiManager.WPS_WEP_PROHIBITED, 0)); + return; + } + } + if (cfgErr != null) { + switch(Integer.parseInt(cfgErr)) { + case CONFIG_AUTH_FAILURE: + mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT, + WifiManager.WPS_AUTH_FAILURE, 0)); + return; + case CONFIG_MULTIPLE_PBC_DETECTED: + mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT, + WifiManager.WPS_OVERLAP_ERROR, 0)); + return; + } + } + } + //For all other errors, return a generic internal error + mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT, + WifiManager.ERROR, 0)); + } + /** * Handle p2p events */ @@ -454,6 +569,8 @@ public class WifiMonitor { mStateMachine.sendMessage(P2P_DEVICE_FOUND_EVENT, new WifiP2pDevice(dataString)); } else if (dataString.startsWith(P2P_DEVICE_LOST_STR)) { mStateMachine.sendMessage(P2P_DEVICE_LOST_EVENT, new WifiP2pDevice(dataString)); + } else if (dataString.startsWith(P2P_FIND_STOPPED_STR)) { + mStateMachine.sendMessage(P2P_FIND_STOPPED_EVENT); } else if (dataString.startsWith(P2P_GO_NEG_REQUEST_STR)) { mStateMachine.sendMessage(P2P_GO_NEGOTIATION_REQUEST_EVENT, new WifiP2pConfig(dataString)); @@ -480,10 +597,23 @@ public class WifiMonitor { mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, nameValue[1]); } else if (dataString.startsWith(P2P_PROV_DISC_PBC_REQ_STR)) { mStateMachine.sendMessage(P2P_PROV_DISC_PBC_REQ_EVENT, - new WifiP2pDevice(dataString)); + new WifiP2pProvDiscEvent(dataString)); + } else if (dataString.startsWith(P2P_PROV_DISC_PBC_RSP_STR)) { + mStateMachine.sendMessage(P2P_PROV_DISC_PBC_RSP_EVENT, + new WifiP2pProvDiscEvent(dataString)); } else if (dataString.startsWith(P2P_PROV_DISC_ENTER_PIN_STR)) { mStateMachine.sendMessage(P2P_PROV_DISC_ENTER_PIN_EVENT, - new WifiP2pDevice(dataString)); + new WifiP2pProvDiscEvent(dataString)); + } else if (dataString.startsWith(P2P_PROV_DISC_SHOW_PIN_STR)) { + mStateMachine.sendMessage(P2P_PROV_DISC_SHOW_PIN_EVENT, + new WifiP2pProvDiscEvent(dataString)); + } else if (dataString.startsWith(P2P_SERV_DISC_RESP_STR)) { + List<WifiP2pServiceResponse> list = WifiP2pServiceResponse.newInstance(dataString); + if (list != null) { + mStateMachine.sendMessage(P2P_SERV_DISC_RESP_EVENT, list); + } else { + Log.e(TAG, "Null service resp " + dataString); + } } } @@ -492,10 +622,12 @@ public class WifiMonitor { */ private void handleHostApEvents(String dataString) { String[] tokens = dataString.split(" "); + /* AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */ if (tokens[0].equals(AP_STA_CONNECTED_STR)) { - mStateMachine.sendMessage(AP_STA_CONNECTED_EVENT, tokens[1]); + mStateMachine.sendMessage(AP_STA_CONNECTED_EVENT, new WifiP2pDevice(dataString)); + /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */ } else if (tokens[0].equals(AP_STA_DISCONNECTED_STR)) { - mStateMachine.sendMessage(AP_STA_DISCONNECTED_EVENT, tokens[1]); + mStateMachine.sendMessage(AP_STA_DISCONNECTED_EVENT, new WifiP2pDevice(dataString)); } } @@ -505,6 +637,9 @@ public class WifiMonitor { * id=network-id state=new-state */ private void handleSupplicantStateChange(String dataString) { + String SSID = null; + int index = dataString.lastIndexOf("SSID="); + if (index != -1) SSID = dataString.substring(index + 5); String[] dataTokens = dataString.split(" "); String BSSID = null; @@ -525,7 +660,6 @@ public class WifiMonitor { try { value = Integer.parseInt(nameValue[1]); } catch (NumberFormatException e) { - Log.w(TAG, "STATE-CHANGE non-integer parameter: " + token); continue; } @@ -548,7 +682,7 @@ public class WifiMonitor { if (newSupplicantState == SupplicantState.INVALID) { Log.w(TAG, "Invalid supplicant state: " + newState); } - notifySupplicantStateChange(networkId, BSSID, newSupplicantState); + notifySupplicantStateChange(networkId, SSID, BSSID, newSupplicantState); } } @@ -597,11 +731,13 @@ public class WifiMonitor { * Send the state machine a notification that the state of the supplicant * has changed. * @param networkId the configured network on which the state change occurred + * @param SSID network name + * @param BSSID network address * @param newState the new {@code SupplicantState} */ - void notifySupplicantStateChange(int networkId, String BSSID, SupplicantState newState) { + void notifySupplicantStateChange(int networkId, String SSID, String BSSID, SupplicantState newState) { mStateMachine.sendMessage(mStateMachine.obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT, - new StateChangeResult(networkId, BSSID, newState))); + new StateChangeResult(networkId, SSID, BSSID, newState))); } /** diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 6ff1bc2..4ec2e02 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -19,6 +19,9 @@ package android.net.wifi; import android.net.wifi.p2p.WifiP2pConfig; import android.net.wifi.p2p.WifiP2pGroup; import android.net.wifi.p2p.WifiP2pDevice; +import android.text.TextUtils; +import android.net.wifi.p2p.nsd.WifiP2pServiceInfo; +import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; import android.util.Log; import java.io.InputStream; @@ -27,30 +30,25 @@ import java.util.ArrayList; import java.util.List; /** - * Native calls for sending requests to the supplicant daemon, and for - * receiving asynchronous events. All methods of the form "xxxxCommand()" - * must be single-threaded, to avoid requests and responses initiated - * from multiple threads from being intermingled. - * <p/> - * Note that methods whose names are not of the form "xxxCommand()" do - * not talk to the supplicant daemon. - * Also, note that all WifiNative calls should happen in the - * WifiStateTracker class except for waitForEvent() call which is - * on a separate monitor channel for WifiMonitor + * Native calls for bring up/shut down of the supplicant daemon and for + * sending requests to the supplicant daemon * - * TODO: clean up the API and move the functionality from JNI to here. We should - * be able to get everything done with doBooleanCommand, doIntCommand and - * doStringCommand native commands + * waitForEvent() is called on the monitor thread for events. All other methods + * must be serialized from the framework. * * {@hide} */ public class WifiNative { + private static final boolean DBG = false; + private final String mTAG; + private static final int DEFAULT_GROUP_OWNER_INTENT = 7; + static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED = 0; static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1; static final int BLUETOOTH_COEXISTENCE_MODE_SENSE = 2; - public native static String getErrorString(int errorCode); + String mInterface = ""; public native static boolean loadDriver(); @@ -58,96 +56,258 @@ public class WifiNative { public native static boolean unloadDriver(); - public native static boolean startSupplicant(); - - public native static boolean startP2pSupplicant(); - - /* Does a graceful shutdown of supplicant. Is a common stop function for both p2p and sta. - * - * Note that underneath we use a harsh-sounding "terminate" supplicant command - * for a graceful stop and a mild-sounding "stop" interface - * to kill the process - */ - public native static boolean stopSupplicant(); + public native static boolean startSupplicant(boolean p2pSupported); /* Sends a kill signal to supplicant. To be used when we have lost connection or when the supplicant is hung */ public native static boolean killSupplicant(); - public native static boolean connectToSupplicant(); + private native boolean connectToSupplicant(String iface); + + private native void closeSupplicantConnection(String iface); + + /** + * Wait for the supplicant to send an event, returning the event string. + * @return the event string sent by the supplicant. + */ + private native String waitForEvent(String iface); + + private native boolean doBooleanCommand(String iface, String command); + + private native int doIntCommand(String iface, String command); + + private native String doStringCommand(String iface, String command); - public native static void closeSupplicantConnection(); + public WifiNative(String iface) { + mInterface = iface; + mTAG = "WifiNative-" + iface; + } - public native static boolean pingCommand(); + public boolean connectToSupplicant() { + return connectToSupplicant(mInterface); + } - public native static boolean scanCommand(boolean forceActive); + public void closeSupplicantConnection() { + closeSupplicantConnection(mInterface); + } - public native static boolean setScanModeCommand(boolean setActive); + public String waitForEvent() { + return waitForEvent(mInterface); + } - public native static String listNetworksCommand(); + private boolean doBooleanCommand(String command) { + if (DBG) Log.d(mTAG, "doBoolean: " + command); + return doBooleanCommand(mInterface, command); + } - public native static int addNetworkCommand(); + private int doIntCommand(String command) { + if (DBG) Log.d(mTAG, "doInt: " + command); + return doIntCommand(mInterface, command); + } - public native static boolean setNetworkVariableCommand(int netId, String name, String value); + private String doStringCommand(String command) { + if (DBG) Log.d(mTAG, "doString: " + command); + return doStringCommand(mInterface, command); + } + + public boolean ping() { + String pong = doStringCommand("PING"); + return (pong != null && pong.equals("PONG")); + } + + public boolean scan() { + return doBooleanCommand("SCAN"); + } + + public boolean setScanMode(boolean setActive) { + if (setActive) { + return doBooleanCommand("DRIVER SCAN-ACTIVE"); + } else { + return doBooleanCommand("DRIVER SCAN-PASSIVE"); + } + } + + /* Does a graceful shutdown of supplicant. Is a common stop function for both p2p and sta. + * + * Note that underneath we use a harsh-sounding "terminate" supplicant command + * for a graceful stop and a mild-sounding "stop" interface + * to kill the process + */ + public boolean stopSupplicant() { + return doBooleanCommand("TERMINATE"); + } + + public String listNetworks() { + return doStringCommand("LIST_NETWORKS"); + } + + public int addNetwork() { + return doIntCommand("ADD_NETWORK"); + } + + public boolean setNetworkVariable(int netId, String name, String value) { + if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false; + return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value); + } - public native static String getNetworkVariableCommand(int netId, String name); + public String getNetworkVariable(int netId, String name) { + if (TextUtils.isEmpty(name)) return null; + return doStringCommand("GET_NETWORK " + netId + " " + name); + } - public native static boolean removeNetworkCommand(int netId); + public boolean removeNetwork(int netId) { + return doBooleanCommand("REMOVE_NETWORK " + netId); + } - public native static boolean enableNetworkCommand(int netId, boolean disableOthers); + public boolean enableNetwork(int netId, boolean disableOthers) { + if (disableOthers) { + return doBooleanCommand("SELECT_NETWORK " + netId); + } else { + return doBooleanCommand("ENABLE_NETWORK " + netId); + } + } - public native static boolean disableNetworkCommand(int netId); + public boolean disableNetwork(int netId) { + return doBooleanCommand("DISABLE_NETWORK " + netId); + } - public native static boolean reconnectCommand(); + public boolean reconnect() { + return doBooleanCommand("RECONNECT"); + } - public native static boolean reassociateCommand(); + public boolean reassociate() { + return doBooleanCommand("REASSOCIATE"); + } - public native static boolean disconnectCommand(); + public boolean disconnect() { + return doBooleanCommand("DISCONNECT"); + } - public native static String statusCommand(); + public String status() { + return doStringCommand("STATUS"); + } - public native static String getMacAddressCommand(); + public String getMacAddress() { + //Macaddr = XX.XX.XX.XX.XX.XX + String ret = doStringCommand("DRIVER MACADDR"); + if (!TextUtils.isEmpty(ret)) { + String[] tokens = ret.split(" = "); + if (tokens.length == 2) return tokens[1]; + } + return null; + } - public native static String scanResultsCommand(); + public String scanResults() { + return doStringCommand("SCAN_RESULTS"); + } - public native static boolean startDriverCommand(); + public boolean startDriver() { + return doBooleanCommand("DRIVER START"); + } - public native static boolean stopDriverCommand(); + public boolean stopDriver() { + return doBooleanCommand("DRIVER STOP"); + } /** * Start filtering out Multicast V4 packets * @return {@code true} if the operation succeeded, {@code false} otherwise + * + * Multicast filtering rules work as follows: + * + * The driver can filter multicast (v4 and/or v6) and broadcast packets when in + * a power optimized mode (typically when screen goes off). + * + * In order to prevent the driver from filtering the multicast/broadcast packets, we have to + * add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective + * + * DRIVER RXFILTER-ADD Num + * where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6 + * + * and DRIVER RXFILTER-START + * In order to stop the usage of these rules, we do + * + * DRIVER RXFILTER-STOP + * DRIVER RXFILTER-REMOVE Num + * where Num is as described for RXFILTER-ADD + * + * The SETSUSPENDOPT driver command overrides the filtering rules */ - public native static boolean startFilteringMulticastV4Packets(); + public boolean startFilteringMulticastV4Packets() { + return doBooleanCommand("DRIVER RXFILTER-STOP") + && doBooleanCommand("DRIVER RXFILTER-REMOVE 2") + && doBooleanCommand("DRIVER RXFILTER-START"); + } /** * Stop filtering out Multicast V4 packets. * @return {@code true} if the operation succeeded, {@code false} otherwise */ - public native static boolean stopFilteringMulticastV4Packets(); + public boolean stopFilteringMulticastV4Packets() { + return doBooleanCommand("DRIVER RXFILTER-STOP") + && doBooleanCommand("DRIVER RXFILTER-ADD 2") + && doBooleanCommand("DRIVER RXFILTER-START"); + } /** * Start filtering out Multicast V6 packets * @return {@code true} if the operation succeeded, {@code false} otherwise */ - public native static boolean startFilteringMulticastV6Packets(); + public boolean startFilteringMulticastV6Packets() { + return doBooleanCommand("DRIVER RXFILTER-STOP") + && doBooleanCommand("DRIVER RXFILTER-REMOVE 3") + && doBooleanCommand("DRIVER RXFILTER-START"); + } /** * Stop filtering out Multicast V6 packets. * @return {@code true} if the operation succeeded, {@code false} otherwise */ - public native static boolean stopFilteringMulticastV6Packets(); + public boolean stopFilteringMulticastV6Packets() { + return doBooleanCommand("DRIVER RXFILTER-STOP") + && doBooleanCommand("DRIVER RXFILTER-ADD 3") + && doBooleanCommand("DRIVER RXFILTER-START"); + } - public native static boolean setPowerModeCommand(int mode); + public int getPowerMode() { + String ret = doStringCommand("DRIVER GETPOWER"); + if (!TextUtils.isEmpty(ret)) { + // reply comes back in the form "powermode = XX" where XX is the + // number we're interested in. + String[] tokens = ret.split(" = "); + try { + if (tokens.length == 2) return Integer.parseInt(tokens[1]); + } catch (NumberFormatException e) { + return -1; + } + } + return -1; + } - public native static int getBandCommand(); + public boolean setPowerMode(int mode) { + return doBooleanCommand("DRIVER POWERMODE " + mode); + } - public native static boolean setBandCommand(int band); + public int getBand() { + String ret = doStringCommand("DRIVER GETBAND"); + if (!TextUtils.isEmpty(ret)) { + //reply is "BAND X" where X is the band + String[] tokens = ret.split(" "); + try { + if (tokens.length == 2) return Integer.parseInt(tokens[1]); + } catch (NumberFormatException e) { + return -1; + } + } + return -1; + } - public native static int getPowerModeCommand(); + public boolean setBand(int band) { + return doBooleanCommand("DRIVER SETBAND " + band); + } - /** + /** * Sets the bluetooth coexistence mode. * * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED}, @@ -155,7 +315,9 @@ public class WifiNative { * {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}. * @return Whether the mode was successfully set. */ - public native static boolean setBluetoothCoexistenceModeCommand(int mode); + public boolean setBluetoothCoexistenceMode(int mode) { + return doBooleanCommand("DRIVER BTCOEXMODE " + mode); + } /** * Enable or disable Bluetooth coexistence scan mode. When this mode is on, @@ -165,43 +327,57 @@ public class WifiNative { * @param isSet whether to enable or disable this mode * @return {@code true} if the command succeeded, {@code false} otherwise. */ - public native static boolean setBluetoothCoexistenceScanModeCommand(boolean setCoexScanMode); - - public native static boolean saveConfigCommand(); - - public native static boolean reloadConfigCommand(); - - public native static boolean setScanResultHandlingCommand(int mode); - - public native static boolean addToBlacklistCommand(String bssid); - - public native static boolean clearBlacklistCommand(); - - public native static boolean startWpsPbcCommand(String bssid); - - public native static boolean startWpsWithPinFromAccessPointCommand(String bssid, String apPin); - - public native static String startWpsWithPinFromDeviceCommand(String bssid); + public boolean setBluetoothCoexistenceScanMode(boolean setCoexScanMode) { + if (setCoexScanMode) { + return doBooleanCommand("DRIVER BTCOEXSCAN-START"); + } else { + return doBooleanCommand("DRIVER BTCOEXSCAN-STOP"); + } + } - public native static boolean setSuspendOptimizationsCommand(boolean enabled); + public boolean saveConfig() { + // Make sure we never write out a value for AP_SCAN other than 1 + return doBooleanCommand("AP_SCAN 1") && doBooleanCommand("SAVE_CONFIG"); + } - public native static boolean setCountryCodeCommand(String countryCode); + public boolean setScanResultHandling(int mode) { + return doBooleanCommand("AP_SCAN " + mode); + } - /** - * Wait for the supplicant to send an event, returning the event string. - * @return the event string sent by the supplicant. - */ - public native static String waitForEvent(); + public boolean addToBlacklist(String bssid) { + if (TextUtils.isEmpty(bssid)) return false; + return doBooleanCommand("BLACKLIST " + bssid); + } - public native static void enableBackgroundScanCommand(boolean enable); + public boolean clearBlacklist() { + return doBooleanCommand("BLACKLIST clear"); + } - public native static void setScanIntervalCommand(int scanInterval); + public boolean setSuspendOptimizations(boolean enabled) { + if (enabled) { + return doBooleanCommand("DRIVER SETSUSPENDMODE 1"); + } else { + return doBooleanCommand("DRIVER SETSUSPENDMODE 0"); + } + } - private native static boolean doBooleanCommand(String command); + public boolean setCountryCode(String countryCode) { + return doBooleanCommand("DRIVER COUNTRY " + countryCode); + } - private native static int doIntCommand(String command); + public void enableBackgroundScan(boolean enable) { + //Note: BGSCAN-START and BGSCAN-STOP are documented in core/res/res/values/config.xml + //and will need an update if the names are changed + if (enable) { + doBooleanCommand("DRIVER BGSCAN-START"); + } else { + doBooleanCommand("DRIVER BGSCAN-STOP"); + } + } - private native static String doStringCommand(String command); + public void setScanInterval(int scanInterval) { + doBooleanCommand("SCAN_INTERVAL " + scanInterval); + } /** Example output: * RSSI=-65 @@ -209,60 +385,137 @@ public class WifiNative { * NOISE=9999 * FREQUENCY=0 */ - public static String signalPoll() { + public String signalPoll() { return doStringCommand("SIGNAL_POLL"); } - public static boolean wpsPbc() { - return doBooleanCommand("WPS_PBC"); + public boolean startWpsPbc(String bssid) { + if (TextUtils.isEmpty(bssid)) { + return doBooleanCommand("WPS_PBC"); + } else { + return doBooleanCommand("WPS_PBC " + bssid); + } + } + + public boolean startWpsPbc(String iface, String bssid) { + if (TextUtils.isEmpty(bssid)) { + return doBooleanCommand("WPS_PBC interface=" + iface); + } else { + return doBooleanCommand("WPS_PBC interface=" + iface + " " + bssid); + } } - public static boolean wpsPin(String pin) { + public boolean startWpsPinKeypad(String pin) { + if (TextUtils.isEmpty(pin)) return false; return doBooleanCommand("WPS_PIN any " + pin); } - public static boolean setPersistentReconnect(boolean enabled) { + public boolean startWpsPinKeypad(String iface, String pin) { + if (TextUtils.isEmpty(pin)) return false; + return doBooleanCommand("WPS_PIN interface=" + iface + " any " + pin); + } + + + public String startWpsPinDisplay(String bssid) { + if (TextUtils.isEmpty(bssid)) { + return doStringCommand("WPS_PIN any"); + } else { + return doStringCommand("WPS_PIN " + bssid); + } + } + + public String startWpsPinDisplay(String iface, String bssid) { + if (TextUtils.isEmpty(bssid)) { + return doStringCommand("WPS_PIN interface=" + iface + " any"); + } else { + return doStringCommand("WPS_PIN interface=" + iface + " " + bssid); + } + } + + /* Configures an access point connection */ + public boolean startWpsRegistrar(String bssid, String pin) { + if (TextUtils.isEmpty(bssid) || TextUtils.isEmpty(pin)) return false; + return doBooleanCommand("WPS_REG " + bssid + " " + pin); + } + + public boolean cancelWps() { + return doBooleanCommand("WPS_CANCEL"); + } + + public boolean setPersistentReconnect(boolean enabled) { int value = (enabled == true) ? 1 : 0; - return WifiNative.doBooleanCommand("SET persistent_reconnect " + value); + return doBooleanCommand("SET persistent_reconnect " + value); + } + + public boolean setDeviceName(String name) { + return doBooleanCommand("SET device_name " + name); + } + + public boolean setDeviceType(String type) { + return doBooleanCommand("SET device_type " + type); + } + + public boolean setConfigMethods(String cfg) { + return doBooleanCommand("SET config_methods " + cfg); + } + + public boolean setP2pSsidPostfix(String postfix) { + return doBooleanCommand("SET p2p_ssid_postfix " + postfix); + } + + public boolean setP2pGroupIdle(String iface, int time) { + return doBooleanCommand("SET interface=" + iface + " p2p_group_idle " + time); } - public static boolean setDeviceName(String name) { - return WifiNative.doBooleanCommand("SET device_name " + name); + public boolean setP2pPowerSave(String iface, boolean enabled) { + if (enabled) { + return doBooleanCommand("P2P_SET interface=" + iface + " ps 1"); + } else { + return doBooleanCommand("P2P_SET interface=" + iface + " ps 0"); + } } - public static boolean setDeviceType(String type) { - return WifiNative.doBooleanCommand("SET device_type " + type); + /** + * "sta" prioritizes STA connection over P2P and "p2p" prioritizes + * P2P connection over STA + */ + public boolean setConcurrencyPriority(String s) { + return doBooleanCommand("P2P_SET conc_priority " + s); } - public static boolean p2pFind() { + public boolean p2pFind() { return doBooleanCommand("P2P_FIND"); } - public static boolean p2pFind(int timeout) { + public boolean p2pFind(int timeout) { if (timeout <= 0) { return p2pFind(); } return doBooleanCommand("P2P_FIND " + timeout); } - public static boolean p2pListen() { + public boolean p2pStopFind() { + return doBooleanCommand("P2P_STOP_FIND"); + } + + public boolean p2pListen() { return doBooleanCommand("P2P_LISTEN"); } - public static boolean p2pListen(int timeout) { + public boolean p2pListen(int timeout) { if (timeout <= 0) { return p2pListen(); } return doBooleanCommand("P2P_LISTEN " + timeout); } - public static boolean p2pFlush() { + public boolean p2pFlush() { return doBooleanCommand("P2P_FLUSH"); } /* p2p_connect <peer device address> <pbc|pin|PIN#> [label|display|keypad] [persistent] [join|auth] [go_intent=<0..15>] [freq=<in MHz>] */ - public static String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) { + public String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) { if (config == null) return null; List<String> args = new ArrayList<String>(); WpsInfo wps = config.wps; @@ -273,8 +526,11 @@ public class WifiNative { args.add("pbc"); break; case WpsInfo.DISPLAY: - //TODO: pass the pin back for display - args.add("pin"); + if (TextUtils.isEmpty(wps.pin)) { + args.add("pin"); + } else { + args.add(wps.pin); + } args.add("display"); break; case WpsInfo.KEYPAD: @@ -295,9 +551,11 @@ public class WifiNative { if (joinExistingGroup) args.add("join"); + //TODO: This can be adapted based on device plugged in state and + //device battery state int groupOwnerIntent = config.groupOwnerIntent; if (groupOwnerIntent < 0 || groupOwnerIntent > 15) { - groupOwnerIntent = 3; //default value + groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT; } args.add("go_intent=" + groupOwnerIntent); @@ -307,26 +565,44 @@ public class WifiNative { return doStringCommand(command); } - public static boolean p2pCancelConnect() { + public boolean p2pCancelConnect() { return doBooleanCommand("P2P_CANCEL"); } - public static boolean p2pGroupAdd() { + public boolean p2pProvisionDiscovery(WifiP2pConfig config) { + if (config == null) return false; + + switch (config.wps.setup) { + case WpsInfo.PBC: + return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " pbc"); + case WpsInfo.DISPLAY: + //We are doing display, so provision discovery is keypad + return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " keypad"); + case WpsInfo.KEYPAD: + //We are doing keypad, so provision discovery is display + return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " display"); + default: + break; + } + return false; + } + + public boolean p2pGroupAdd() { return doBooleanCommand("P2P_GROUP_ADD"); } - public static boolean p2pGroupRemove(String iface) { - if (iface == null) return false; + public boolean p2pGroupRemove(String iface) { + if (TextUtils.isEmpty(iface)) return false; return doBooleanCommand("P2P_GROUP_REMOVE " + iface); } - public static boolean p2pReject(String deviceAddress) { + public boolean p2pReject(String deviceAddress) { return doBooleanCommand("P2P_REJECT " + deviceAddress); } /* Invite a peer to a group */ - public static boolean p2pInvite(WifiP2pGroup group, String deviceAddress) { - if (deviceAddress == null) return false; + public boolean p2pInvite(WifiP2pGroup group, String deviceAddress) { + if (TextUtils.isEmpty(deviceAddress)) return false; if (group == null) { return doBooleanCommand("P2P_INVITE peer=" + deviceAddress); @@ -337,35 +613,15 @@ public class WifiNative { } /* Reinvoke a persistent connection */ - public static boolean p2pReinvoke(int netId, String deviceAddress) { - if (deviceAddress == null || netId < 0) return false; + public boolean p2pReinvoke(int netId, String deviceAddress) { + if (TextUtils.isEmpty(deviceAddress) || netId < 0) return false; return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress); } - public static String p2pGetInterfaceAddress(String deviceAddress) { - if (deviceAddress == null) return null; - - // "p2p_peer deviceAddress" returns a multi-line result containing - // intended_addr=fa:7b:7a:42:82:13 - String peerInfo = p2pPeer(deviceAddress); - if (peerInfo == null) return null; - String[] tokens= peerInfo.split("\n"); - - for (String token : tokens) { - //TODO: update from interface_addr when wpa_supplicant implementation is fixed - if (token.startsWith("intended_addr=")) { - String[] nameValue = token.split("="); - if (nameValue.length != 2) break; - return nameValue[1]; - } - } - return null; - } - - public static String p2pGetDeviceAddress() { - String status = statusCommand(); + public String p2pGetDeviceAddress() { + String status = status(); if (status == null) return ""; String[] tokens = status.split("\n"); @@ -379,7 +635,88 @@ public class WifiNative { return ""; } - public static String p2pPeer(String deviceAddress) { + public boolean isGroupOwner(String deviceAddress) { + /* BSS returns details only for a GO */ + String bssInfo = doStringCommand("BSS p2p_dev_addr=" + deviceAddress); + if (TextUtils.isEmpty(bssInfo)) return false; + return true; + } + + public String p2pPeer(String deviceAddress) { return doStringCommand("P2P_PEER " + deviceAddress); } + + public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) { + /* + * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump> + * P2P_SERVICE_ADD upnp <version hex> <service> + * + * e.g) + * [Bonjour] + * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.) + * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027 + * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript) + * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001 + * 09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074 + * + * [UPnP] + * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012 + * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice + * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp + * -org:device:InternetGatewayDevice:1 + * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp + * -org:service:ContentDirectory:2 + */ + for (String s : servInfo.getSupplicantQueryList()) { + String command = "P2P_SERVICE_ADD"; + command += (" " + s); + if (!doBooleanCommand(command)) { + return false; + } + } + return true; + } + + public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) { + /* + * P2P_SERVICE_DEL bonjour <query hexdump> + * P2P_SERVICE_DEL upnp <version hex> <service> + */ + for (String s : servInfo.getSupplicantQueryList()) { + String command = "P2P_SERVICE_DEL "; + + String[] data = s.split(" "); + if (data.length < 2) { + return false; + } + if ("upnp".equals(data[0])) { + command += s; + } else if ("bonjour".equals(data[0])) { + command += data[0]; + command += (" " + data[1]); + } else { + return false; + } + if (!doBooleanCommand(command)) { + return false; + } + } + return true; + } + + public boolean p2pServiceFlush() { + return doBooleanCommand("P2P_SERVICE_FLUSH"); + } + + public String p2pServDiscReq(String addr, String query) { + String command = "P2P_SERV_DISC_REQ"; + command += (" " + addr); + command += (" " + query); + + return doStringCommand(command); + } + + public boolean p2pServDiscCancelReq(String id) { + return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id); + } } diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index df622d6..2f14098 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -45,6 +45,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.DhcpInfo; import android.net.DhcpInfoInternal; @@ -113,15 +114,22 @@ public class WifiStateMachine extends StateMachine { private static final String SOFTAP_IFACE = "wl0.1"; private WifiMonitor mWifiMonitor; + private WifiNative mWifiNative; + private WifiConfigStore mWifiConfigStore; private INetworkManagementService mNwService; private ConnectivityManager mCm; + private final boolean mP2pSupported; + /* Scan results handling */ private List<ScanResult> mScanResults; private static final Pattern scanResultPattern = Pattern.compile("\t+"); private static final int SCAN_RESULT_CACHE_SIZE = 80; private final LruCache<String, ScanResult> mScanResultCache; + /* Chipset supports background scan */ + private final boolean mBackgroundScanSupported; + private String mInterfaceName; /* Tethering interface could be seperate from wlan interface */ private String mTetherInterfaceName; @@ -135,9 +143,17 @@ public class WifiStateMachine extends StateMachine { private int mReconnectCount = 0; private boolean mIsScanMode = false; private boolean mScanResultIsPending = false; + /* Tracks if the current scan settings are active */ + private boolean mSetScanActive = false; + /* High perf mode is true if an app has held a high perf Wifi Lock */ + private boolean mHighPerfMode = false; private boolean mBluetoothConnectionActive = false; + private BroadcastReceiver mScreenReceiver; + private IntentFilter mScreenFilter; + private PowerManager.WakeLock mSuspendWakeLock; + /** * Interval in milliseconds between polling for RSSI * and linkspeed information @@ -177,7 +193,6 @@ public class WifiStateMachine extends StateMachine { private WifiInfo mWifiInfo; private NetworkInfo mNetworkInfo; private SupplicantStateTracker mSupplicantStateTracker; - private WpsStateMachine mWpsStateMachine; private DhcpStateMachine mDhcpStateMachine; private AlarmManager mAlarmManager; @@ -268,14 +283,14 @@ public class WifiStateMachine extends StateMachine { static final int CMD_ENABLE_NETWORK = BASE + 54; /* Enable all networks */ static final int CMD_ENABLE_ALL_NETWORKS = BASE + 55; - /* Disable a network. The device does not attempt a connection to the given network. */ - static final int CMD_DISABLE_NETWORK = BASE + 56; /* Blacklist network. De-prioritizes the given BSSID for connection. */ - static final int CMD_BLACKLIST_NETWORK = BASE + 57; + static final int CMD_BLACKLIST_NETWORK = BASE + 56; /* Clear the blacklist network list */ - static final int CMD_CLEAR_BLACKLIST = BASE + 58; + static final int CMD_CLEAR_BLACKLIST = BASE + 57; /* Save configuration */ - static final int CMD_SAVE_CONFIG = BASE + 59; + static final int CMD_SAVE_CONFIG = BASE + 58; + /* Get configured networks*/ + static final int CMD_GET_CONFIGURED_NETWORKS = BASE + 59; /* Supplicant commands after driver start*/ /* Initiate a scan */ @@ -314,33 +329,16 @@ public class WifiStateMachine extends StateMachine { static final int CMD_START_PACKET_FILTERING = BASE + 84; /* Clear packet filter */ static final int CMD_STOP_PACKET_FILTERING = BASE + 85; + /* Set suspend mode optimizations in the driver */ + static final int CMD_SET_SUSPEND_OPTIMIZATIONS = BASE + 86; + /* Clear suspend mode optimizations in the driver */ + static final int CMD_CLEAR_SUSPEND_OPTIMIZATIONS = BASE + 87; /* arg1 values to CMD_STOP_PACKET_FILTERING and CMD_START_PACKET_FILTERING */ static final int MULTICAST_V6 = 1; static final int MULTICAST_V4 = 0; - /* Connect to a specified network (network id - * or WifiConfiguration) This involves increasing - * the priority of the network, enabling the network - * (while disabling others) and issuing a reconnect. - * Note that CMD_RECONNECT just does a reconnect to - * an existing network. All the networks get enabled - * upon a successful connection or a failure. - */ - static final int CMD_CONNECT_NETWORK = BASE + 86; - /* Save the specified network. This involves adding - * an enabled network (if new) and updating the - * config and issuing a save on supplicant config. - */ - static final int CMD_SAVE_NETWORK = BASE + 87; - /* Delete the specified network. This involves - * removing the network and issuing a save on - * supplicant config. - */ - static final int CMD_FORGET_NETWORK = BASE + 88; - /* Start Wi-Fi protected setup */ - static final int CMD_START_WPS = BASE + 89; - /* Set the frequency band */ + /* Set the frequency band */ static final int CMD_SET_FREQUENCY_BAND = BASE + 90; /* Enable background scan for configured networks */ static final int CMD_ENABLE_BACKGROUND_SCAN = BASE + 91; @@ -349,15 +347,9 @@ public class WifiStateMachine extends StateMachine { /* Reset the supplicant state tracker */ static final int CMD_RESET_SUPPLICANT_STATE = BASE + 111; - /* Commands/events reported by WpsStateMachine */ - /* Indicates the completion of WPS activity */ - static final int WPS_COMPLETED_EVENT = BASE + 121; - /* Reset the WPS state machine */ - static final int CMD_RESET_WPS_STATE = BASE + 122; - - /* Interaction with WifiP2pService */ - public static final int WIFI_ENABLE_PENDING = BASE + 131; - public static final int P2P_ENABLE_PROCEED = BASE + 132; + /* P2p commands */ + public static final int CMD_ENABLE_P2P = BASE + 131; + public static final int CMD_DISABLE_P2P = BASE + 132; private static final int CONNECT_MODE = 1; private static final int SCAN_ONLY_MODE = 2; @@ -454,8 +446,12 @@ public class WifiStateMachine extends StateMachine { private State mScanModeState = new ScanModeState(); /* Connecting to an access point */ private State mConnectModeState = new ConnectModeState(); - /* Fetching IP after network connection (assoc+auth complete) */ - private State mConnectingState = new ConnectingState(); + /* Connected at 802.11 (L2) level */ + private State mL2ConnectedState = new L2ConnectedState(); + /* fetching IP after connection to access point (assoc+auth complete) */ + private State mObtainingIpState = new ObtainingIpState(); + /* Waiting for link quality verification to be complete */ + private State mVerifyingLinkState = new VerifyingLinkState(); /* Connected with IP addr */ private State mConnectedState = new ConnectedState(); /* disconnect issued, waiting for network disconnect confirmation */ @@ -463,7 +459,7 @@ public class WifiStateMachine extends StateMachine { /* Network is not connected, supplicant assoc+auth is not complete */ private State mDisconnectedState = new DisconnectedState(); /* Waiting for WPS to be completed*/ - private State mWaitForWpsCompletionState = new WaitForWpsCompletionState(); + private State mWpsRunningState = new WpsRunningState(); /* Soft ap is starting up */ private State mSoftApStartingState = new SoftApStartingState(); @@ -476,9 +472,6 @@ public class WifiStateMachine extends StateMachine { /* Waiting for untether confirmation to stop soft Ap */ private State mSoftApStoppingState = new SoftApStoppingState(); - /* Wait till p2p is disabled */ - private State mWaitForP2pDisableState = new WaitForP2pDisableState(); - private class TetherStateChange { ArrayList<String> available; ArrayList<String> active; @@ -537,11 +530,6 @@ public class WifiStateMachine extends StateMachine { private final WorkSource mLastRunningWifiUids = new WorkSource(); private final IBatteryStats mBatteryStats; - private boolean mNextWifiActionExplicit = false; - private int mLastExplicitNetworkId; - private long mLastNetworkChoiceTime; - private static final long EXPLICIT_CONNECT_ALLOWED_DELAY_MS = 2 * 60 * 1000; - public WifiStateMachine(Context context, String wlanInterface) { super(TAG); @@ -555,11 +543,16 @@ public class WifiStateMachine extends StateMachine { IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); mNwService = INetworkManagementService.Stub.asInterface(b); - mWifiMonitor = new WifiMonitor(this); + mP2pSupported = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WIFI_DIRECT); + + mWifiNative = new WifiNative(mInterfaceName); + mWifiConfigStore = new WifiConfigStore(context, mWifiNative); + mWifiMonitor = new WifiMonitor(this, mWifiNative); mDhcpInfoInternal = new DhcpInfoInternal(); mWifiInfo = new WifiInfo(); - mSupplicantStateTracker = new SupplicantStateTracker(context, this, getHandler()); - mWpsStateMachine = new WpsStateMachine(context, this, getHandler()); + mSupplicantStateTracker = new SupplicantStateTracker(context, this, mWifiConfigStore, + getHandler()); mLinkProperties = new LinkProperties(); WifiApConfigStore wifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore( @@ -586,6 +579,9 @@ public class WifiStateMachine extends StateMachine { mDriverStopDelayMs = mContext.getResources().getInteger( com.android.internal.R.integer.config_wifi_driver_stop_delay); + mBackgroundScanSupported = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_wifi_background_scan_support); + mContext.registerReceiver( new BroadcastReceiver() { @Override @@ -607,11 +603,41 @@ public class WifiStateMachine extends StateMachine { }, new IntentFilter(ACTION_START_SCAN)); + mScreenFilter = new IntentFilter(); + mScreenFilter.addAction(Intent.ACTION_SCREEN_ON); + mScreenFilter.addAction(Intent.ACTION_SCREEN_OFF); + mScreenReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(Intent.ACTION_SCREEN_ON)) { + enableRssiPolling(true); + if (mBackgroundScanSupported) { + enableBackgroundScanCommand(false); + } + enableAllNetworks(); + sendMessage(CMD_CLEAR_SUSPEND_OPTIMIZATIONS); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + enableRssiPolling(false); + if (mBackgroundScanSupported) { + enableBackgroundScanCommand(true); + } + //Allow 2s for suspend optimizations to be set + mSuspendWakeLock.acquire(2000); + sendMessage(CMD_SET_SUSPEND_OPTIMIZATIONS); + } + } + }; + mScanResultCache = new LruCache<String, ScanResult>(SCAN_RESULT_CACHE_SIZE); PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mSuspendWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WifiSuspend"); + mSuspendWakeLock.setReferenceCounted(false); + addState(mDefaultState); addState(mInitialState, mDefaultState); addState(mDriverUnloadingState, mDefaultState); @@ -625,11 +651,13 @@ public class WifiStateMachine extends StateMachine { addState(mDriverStartedState, mSupplicantStartedState); addState(mScanModeState, mDriverStartedState); addState(mConnectModeState, mDriverStartedState); - addState(mConnectingState, mConnectModeState); - addState(mConnectedState, mConnectModeState); + addState(mL2ConnectedState, mConnectModeState); + addState(mObtainingIpState, mL2ConnectedState); + addState(mVerifyingLinkState, mL2ConnectedState); + addState(mConnectedState, mL2ConnectedState); addState(mDisconnectingState, mConnectModeState); addState(mDisconnectedState, mConnectModeState); - addState(mWaitForWpsCompletionState, mConnectModeState); + addState(mWpsRunningState, mConnectModeState); addState(mDriverStoppingState, mSupplicantStartedState); addState(mDriverStoppedState, mSupplicantStartedState); addState(mSupplicantStoppingState, mDefaultState); @@ -638,10 +666,10 @@ public class WifiStateMachine extends StateMachine { addState(mTetheringState, mSoftApStartedState); addState(mTetheredState, mSoftApStartedState); addState(mSoftApStoppingState, mDefaultState); - addState(mWaitForP2pDisableState, mDefaultState); setInitialState(mInitialState); + setProcessedMessagesSize(100); if (DBG) setDbg(true); //start the state machine @@ -652,6 +680,9 @@ public class WifiStateMachine extends StateMachine { * Methods exposed for public use ********************************************************/ + public Messenger getMessenger() { + return new Messenger(getHandler()); + } /** * TODO: doc */ @@ -855,8 +886,11 @@ public class WifiStateMachine extends StateMachine { return result; } - public List<WifiConfiguration> syncGetConfiguredNetworks() { - return WifiConfigStore.getConfiguredNetworks(); + public List<WifiConfiguration> syncGetConfiguredNetworks(AsyncChannel channel) { + Message resultMsg = channel.sendMessageSynchronously(CMD_GET_CONFIGURED_NETWORKS); + List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj; + resultMsg.recycle(); + return result; } /** @@ -893,9 +927,8 @@ public class WifiStateMachine extends StateMachine { * @return {@code true} if the operation succeeds, {@code false} otherwise */ public boolean syncDisableNetwork(AsyncChannel channel, int netId) { - Message resultMsg = channel.sendMessageSynchronously(CMD_DISABLE_NETWORK, netId, - WifiConfiguration.DISABLED_UNKNOWN_REASON); - boolean result = (resultMsg.arg1 != FAILURE); + Message resultMsg = channel.sendMessageSynchronously(WifiManager.DISABLE_NETWORK, netId); + boolean result = (resultMsg.arg1 != WifiManager.DISABLE_NETWORK_FAILED); resultMsg.recycle(); return result; } @@ -918,39 +951,6 @@ public class WifiStateMachine extends StateMachine { sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST)); } - public void connectNetwork(int netId) { - sendMessage(obtainMessage(CMD_CONNECT_NETWORK, netId, 0)); - } - - public void connectNetwork(WifiConfiguration wifiConfig) { - /* arg1 is used to indicate netId, force a netId value of - * WifiConfiguration.INVALID_NETWORK_ID when we are passing - * a configuration since the default value of 0 is a valid netId - */ - sendMessage(obtainMessage(CMD_CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID, - 0, wifiConfig)); - } - - public void saveNetwork(WifiConfiguration wifiConfig) { - sendMessage(obtainMessage(CMD_SAVE_NETWORK, wifiConfig)); - } - - public void forgetNetwork(int netId) { - sendMessage(obtainMessage(CMD_FORGET_NETWORK, netId, 0)); - } - - public void disableNetwork(Messenger replyTo, int netId, int reason) { - Message message = obtainMessage(CMD_DISABLE_NETWORK, netId, reason); - message.replyTo = replyTo; - sendMessage(message); - } - - public void startWps(Messenger replyTo, WpsInfo config) { - Message msg = obtainMessage(CMD_START_WPS, config); - msg.replyTo = replyTo; - sendMessage(msg); - } - public void enableRssiPolling(boolean enabled) { sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0)); } @@ -1042,7 +1042,7 @@ public class WifiStateMachine extends StateMachine { * Returns the wifi configuration file */ public String getConfigFile() { - return WifiConfigStore.getConfigFile(); + return mWifiConfigStore.getConfigFile(); } /** @@ -1116,12 +1116,43 @@ public class WifiStateMachine extends StateMachine { sb.append("mReconnectCount ").append(mReconnectCount).append(LS); sb.append("mIsScanMode ").append(mIsScanMode).append(LS); sb.append("Supplicant status").append(LS) - .append(WifiNative.statusCommand()).append(LS).append(LS); + .append(mWifiNative.status()).append(LS).append(LS); - sb.append(WifiConfigStore.dump()); + sb.append(mWifiConfigStore.dump()); return sb.toString(); } + @Override + protected boolean recordProcessedMessage(Message msg) { + //Ignore screen on/off & common messages when driver has started + if (getCurrentState() == mConnectedState || getCurrentState() == mDisconnectedState) { + switch (msg.what) { + case CMD_LOAD_DRIVER: + case CMD_START_SUPPLICANT: + case CMD_START_DRIVER: + case CMD_SET_SCAN_MODE: + case CMD_SET_HIGH_PERF_MODE: + case CMD_SET_SUSPEND_OPTIMIZATIONS: + case CMD_CLEAR_SUSPEND_OPTIMIZATIONS: + case CMD_ENABLE_BACKGROUND_SCAN: + case CMD_ENABLE_ALL_NETWORKS: + return false; + } + } + + switch (msg.what) { + case CMD_START_SCAN: + case CMD_ENABLE_RSSI_POLL: + case CMD_RSSI_POLL: + case CMD_DELAYED_STOP_DRIVER: + case WifiMonitor.SCAN_RESULTS_EVENT: + case WifiWatchdogStateMachine.RSSI_FETCH: + return false; + default: + return true; + } + } + /********************************************************* * Internal private functions ********************************************************/ @@ -1149,9 +1180,9 @@ public class WifiStateMachine extends StateMachine { ifcg = mNwService.getInterfaceConfig(intf); if (ifcg != null) { /* IP/netmask: 192.168.43.1/255.255.255.0 */ - ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress( - "192.168.43.1"), 24); - ifcg.interfaceFlags = "[up]"; + ifcg.setLinkAddress(new LinkAddress( + NetworkUtils.numericToInetAddress("192.168.43.1"), 24)); + ifcg.setInterfaceUp(); mNwService.setInterfaceConfig(intf, ifcg); } @@ -1183,8 +1214,8 @@ public class WifiStateMachine extends StateMachine { try { ifcg = mNwService.getInterfaceConfig(mInterfaceName); if (ifcg != null) { - ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress( - "0.0.0.0"), 0); + ifcg.setLinkAddress( + new LinkAddress(NetworkUtils.numericToInetAddress("0.0.0.0"), 0)); mNwService.setInterfaceConfig(mInterfaceName, ifcg); } } catch (Exception e) { @@ -1410,23 +1441,6 @@ public class WifiStateMachine extends StateMachine { mScanResults = scanList; } - private String fetchSSID() { - String status = WifiNative.statusCommand(); - if (status == null) { - return null; - } - // extract ssid from a series of "name=value" - String[] lines = status.split("\n"); - for (String line : lines) { - String[] prop = line.split(" *= *"); - if (prop.length < 2) continue; - String name = prop[0]; - String value = prop[1]; - if (name.equalsIgnoreCase("ssid")) return value; - } - return null; - } - /* * Fetch RSSI and linkspeed on current connection */ @@ -1434,7 +1448,7 @@ public class WifiStateMachine extends StateMachine { int newRssi = -1; int newLinkSpeed = -1; - String signalPoll = WifiNative.signalPoll(); + String signalPoll = mWifiNative.signalPoll(); if (signalPoll != null) { String[] lines = signalPoll.split("\n"); @@ -1465,14 +1479,11 @@ public class WifiStateMachine extends StateMachine { * be displayed in the status bar, and only send the * broadcast if that much more coarse-grained number * changes. This cuts down greatly on the number of - * broadcasts, at the cost of not mWifiInforming others + * broadcasts, at the cost of not informing others * interested in RSSI of all the changes in signal * level. */ - // TODO: The second arg to the call below needs to be a symbol somewhere, but - // it's actually the size of an array of icons that's private - // to StatusBar Policy. - int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4); + int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, WifiManager.RSSI_LEVELS); if (newSignalLevel != mLastSignalLevel) { sendRssiChangeBroadcast(newRssi); } @@ -1486,29 +1497,14 @@ public class WifiStateMachine extends StateMachine { } } - private void setHighPerfModeEnabledNative(boolean enable) { - if(!WifiNative.setSuspendOptimizationsCommand(!enable)) { - loge("set suspend optimizations failed!"); - } - if (enable) { - if (!WifiNative.setPowerModeCommand(POWER_MODE_ACTIVE)) { - loge("set power mode active failed!"); - } - } else { - if (!WifiNative.setPowerModeCommand(POWER_MODE_AUTO)) { - loge("set power mode auto failed!"); - } - } - } - private void configureLinkProperties() { - if (WifiConfigStore.isUsingStaticIp(mLastNetworkId)) { - mLinkProperties = WifiConfigStore.getLinkProperties(mLastNetworkId); + if (mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { + mLinkProperties = mWifiConfigStore.getLinkProperties(mLastNetworkId); } else { synchronized (mDhcpInfoInternal) { mLinkProperties = mDhcpInfoInternal.makeLinkProperties(); } - mLinkProperties.setHttpProxy(WifiConfigStore.getProxyProperties(mLastNetworkId)); + mLinkProperties.setHttpProxy(mWifiConfigStore.getProxyProperties(mLastNetworkId)); } mLinkProperties.setInterfaceName(mInterfaceName); if (DBG) { @@ -1540,22 +1536,17 @@ public class WifiStateMachine extends StateMachine { Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo)); intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties (mLinkProperties)); if (bssid != null) intent.putExtra(WifiManager.EXTRA_BSSID, bssid); - if (mNetworkInfo.getState() == NetworkInfo.State.CONNECTED) + if (mNetworkInfo.getDetailedState() == DetailedState.VERIFYING_POOR_LINK || + mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) { intent.putExtra(WifiManager.EXTRA_WIFI_INFO, new WifiInfo(mWifiInfo)); + } mContext.sendStickyBroadcast(intent); } - private void sendErrorBroadcast(int errorCode) { - Intent intent = new Intent(WifiManager.ERROR_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_ERROR_CODE, errorCode); - mContext.sendBroadcast(intent); - } - private void sendLinkConfigurationChangedBroadcast() { Intent intent = new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); @@ -1610,9 +1601,9 @@ public class WifiStateMachine extends StateMachine { /* BSSID is valid only in ASSOCIATING state */ mWifiInfo.setBSSID(stateChangeResult.BSSID); } + mWifiInfo.setSSID(stateChangeResult.SSID); mSupplicantStateTracker.sendMessage(Message.obtain(message)); - mWpsStateMachine.sendMessage(Message.obtain(message)); return state; } @@ -1628,6 +1619,10 @@ public class WifiStateMachine extends StateMachine { * stop DHCP */ if (mDhcpStateMachine != null) { + /* In case we were in middle of DHCP operation + restore back powermode */ + handlePostDhcpSetup(); + mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP); mDhcpStateMachine.quit(); mDhcpStateMachine = null; @@ -1647,17 +1642,18 @@ public class WifiStateMachine extends StateMachine { mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID); mWifiInfo.setRssi(MIN_RSSI); mWifiInfo.setLinkSpeed(-1); - mWifiInfo.setExplicitConnect(false); - /* send event to CM & network change broadcast */ setNetworkDetailedState(DetailedState.DISCONNECTED); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED); + + /* send event to CM & network change broadcast */ sendNetworkStateChangeBroadcast(mLastBssid); /* Clear network properties */ mLinkProperties.clear(); /* Clear IP settings if the network used DHCP */ - if (!WifiConfigStore.isUsingStaticIp(mLastNetworkId)) { - WifiConfigStore.clearIpConfiguration(mLastNetworkId); + if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { + mWifiConfigStore.clearIpConfiguration(mLastNetworkId); } mLastBssid= null; @@ -1683,29 +1679,29 @@ public class WifiStateMachine extends StateMachine { * coexistence would interrupt that connection. */ // Disable the coexistence mode - WifiNative.setBluetoothCoexistenceModeCommand( - WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED); + mWifiNative.setBluetoothCoexistenceMode( + mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED); } - mPowerMode = WifiNative.getPowerModeCommand(); + mPowerMode = mWifiNative.getPowerMode(); if (mPowerMode < 0) { // Handle the case where supplicant driver does not support // getPowerModeCommand. mPowerMode = WifiStateMachine.POWER_MODE_AUTO; } if (mPowerMode != WifiStateMachine.POWER_MODE_ACTIVE) { - WifiNative.setPowerModeCommand(WifiStateMachine.POWER_MODE_ACTIVE); + mWifiNative.setPowerMode(WifiStateMachine.POWER_MODE_ACTIVE); } } void handlePostDhcpSetup() { /* restore power mode */ - WifiNative.setPowerModeCommand(mPowerMode); + mWifiNative.setPowerMode(mPowerMode); // Set the coexistence mode back to its default value - WifiNative.setBluetoothCoexistenceModeCommand( - WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE); + mWifiNative.setBluetoothCoexistenceMode( + mWifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE); } private void handleSuccessfulIpConfiguration(DhcpInfoInternal dhcpInfoInternal) { @@ -1714,13 +1710,13 @@ public class WifiStateMachine extends StateMachine { } mLastSignalLevel = -1; // force update of signal strength mReconnectCount = 0; //Reset IP failure tracking - WifiConfigStore.setIpConfiguration(mLastNetworkId, dhcpInfoInternal); + mWifiConfigStore.setIpConfiguration(mLastNetworkId, dhcpInfoInternal); InetAddress addr = NetworkUtils.numericToInetAddress(dhcpInfoInternal.ipAddress); mWifiInfo.setInetAddress(addr); if (getNetworkDetailedState() == DetailedState.CONNECTED) { //DHCP renewal in connected state LinkProperties linkProperties = dhcpInfoInternal.makeLinkProperties(); - linkProperties.setHttpProxy(WifiConfigStore.getProxyProperties(mLastNetworkId)); + linkProperties.setHttpProxy(mWifiConfigStore.getProxyProperties(mLastNetworkId)); linkProperties.setInterfaceName(mInterfaceName); if (!linkProperties.equals(mLinkProperties)) { if (DBG) { @@ -1732,8 +1728,6 @@ public class WifiStateMachine extends StateMachine { } } else { configureLinkProperties(); - setNetworkDetailedState(DetailedState.CONNECTED); - sendNetworkStateChangeBroadcast(mLastBssid); } } @@ -1750,7 +1744,7 @@ public class WifiStateMachine extends StateMachine { if (maxRetries > 0 && ++mReconnectCount > maxRetries) { loge("Failed " + mReconnectCount + " times, Disabling " + mLastNetworkId); - WifiConfigStore.disableNetwork(mLastNetworkId, + mWifiConfigStore.disableNetwork(mLastNetworkId, WifiConfiguration.DISABLED_DHCP_FAILURE); mReconnectCount = 0; } @@ -1758,8 +1752,8 @@ public class WifiStateMachine extends StateMachine { /* DHCP times out after about 30 seconds, we do a * disconnect and an immediate reconnect to try again */ - WifiNative.disconnectCommand(); - WifiNative.reconnectCommand(); + mWifiNative.disconnect(); + mWifiNative.reconnect(); } /* Current design is to not set the config on a running hostapd but instead @@ -1819,11 +1813,14 @@ public class WifiStateMachine extends StateMachine { /* Synchronous call returns */ case CMD_PING_SUPPLICANT: case CMD_ENABLE_NETWORK: - case CMD_DISABLE_NETWORK: case CMD_ADD_OR_UPDATE_NETWORK: case CMD_REMOVE_NETWORK: case CMD_SAVE_CONFIG: - mReplyChannel.replyToMessage(message, message.what, FAILURE); + replyToMessage(message, message.what, FAILURE); + break; + case CMD_GET_CONFIGURED_NETWORKS: + replyToMessage(message, message.what, + mWifiConfigStore.getConfiguredNetworks()); break; case CMD_ENABLE_RSSI_POLL: mEnableRssiPolling = (message.arg1 == 1); @@ -1831,6 +1828,9 @@ public class WifiStateMachine extends StateMachine { case CMD_ENABLE_BACKGROUND_SCAN: mEnableBackgroundScan = (message.arg1 == 1); break; + case CMD_SET_HIGH_PERF_MODE: + mHighPerfMode = (message.arg1 == 1); + break; /* Discard */ case CMD_LOAD_DRIVER: case CMD_UNLOAD_DRIVER: @@ -1862,12 +1862,8 @@ public class WifiStateMachine extends StateMachine { case CMD_CLEAR_BLACKLIST: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: - case CMD_CONNECT_NETWORK: - case CMD_SAVE_NETWORK: - case CMD_FORGET_NETWORK: case CMD_RSSI_POLL: case CMD_ENABLE_ALL_NETWORKS: case DhcpStateMachine.CMD_PRE_DHCP_ACTION: @@ -1877,20 +1873,43 @@ public class WifiStateMachine extends StateMachine { case CMD_SET_AP_CONFIG_COMPLETED: case CMD_REQUEST_AP_CONFIG: case CMD_RESPONSE_AP_CONFIG: + case WifiWatchdogStateMachine.POOR_LINK_DETECTED: + case WifiWatchdogStateMachine.GOOD_LINK_DETECTED: + case CMD_CLEAR_SUSPEND_OPTIMIZATIONS: + break; + case CMD_SET_SUSPEND_OPTIMIZATIONS: + mSuspendWakeLock.release(); break; case WifiMonitor.DRIVER_HUNG_EVENT: setWifiEnabled(false); setWifiEnabled(true); break; - case CMD_START_WPS: - /* Return failure when the state machine cannot handle WPS initiation*/ - mReplyChannel.replyToMessage(message, WifiManager.CMD_WPS_COMPLETED, - new WpsResult(Status.FAILURE)); + case WifiManager.CONNECT_NETWORK: + replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED, + WifiManager.BUSY); break; - case WifiP2pService.P2P_ENABLE_PENDING: - // turn off wifi and defer to be handled in DriverUnloadedState - setWifiEnabled(false); - deferMessage(message); + case WifiManager.FORGET_NETWORK: + replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED, + WifiManager.BUSY); + break; + case WifiManager.SAVE_NETWORK: + replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, + WifiManager.BUSY); + break; + case WifiManager.START_WPS: + replyToMessage(message, WifiManager.WPS_FAILED, + WifiManager.BUSY); + break; + case WifiManager.CANCEL_WPS: + replyToMessage(message, WifiManager.CANCEL_WPS_FAILED, + WifiManager.BUSY); + break; + case WifiManager.DISABLE_NETWORK: + replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED, + WifiManager.BUSY); + break; + case WifiWatchdogStateMachine.RSSI_FETCH: + replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_FAILED); break; default: loge("Error! unhandled message" + message); @@ -1910,7 +1929,7 @@ public class WifiStateMachine extends StateMachine { // 50021 wifi_state_changed (custom|1|5) EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - if (WifiNative.isDriverLoaded()) { + if (mWifiNative.isDriverLoaded()) { transitionTo(mDriverLoadedState); } else { @@ -1963,7 +1982,7 @@ public class WifiStateMachine extends StateMachine { break; } - if(WifiNative.loadDriver()) { + if(mWifiNative.loadDriver()) { if (DBG) log("Driver load successful"); sendMessage(CMD_LOAD_DRIVER_SUCCESS); } else { @@ -2003,7 +2022,6 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2013,7 +2031,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2051,7 +2068,7 @@ public class WifiStateMachine extends StateMachine { loge("Unable to change interface settings: " + ie); } - if(WifiNative.startSupplicant()) { + if(mWifiNative.startSupplicant(mP2pSupported)) { if (DBG) log("Supplicant start successful"); mWifiMonitor.startMonitoring(); transitionTo(mSupplicantStartingState); @@ -2066,7 +2083,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2083,7 +2099,7 @@ public class WifiStateMachine extends StateMachine { public void run() { if (DBG) log(getName() + message.toString() + "\n"); mWakeLock.acquire(); - if(WifiNative.unloadDriver()) { + if(mWifiNative.unloadDriver()) { if (DBG) log("Driver unload successful"); sendMessage(CMD_UNLOAD_DRIVER_SUCCESS); @@ -2137,7 +2153,6 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2147,7 +2162,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2163,16 +2177,11 @@ public class WifiStateMachine extends StateMachine { if (DBG) log(getName() + message.toString() + "\n"); switch (message.what) { case CMD_LOAD_DRIVER: - mWifiP2pChannel.sendMessage(WIFI_ENABLE_PENDING); - transitionTo(mWaitForP2pDisableState); - break; - case WifiP2pService.P2P_ENABLE_PENDING: - mReplyChannel.replyToMessage(message, P2P_ENABLE_PROCEED); + transitionTo(mDriverLoadingState); break; default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2208,15 +2217,14 @@ public class WifiStateMachine extends StateMachine { /* Reset the supplicant state to indicate the supplicant * state is not known at this time */ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE); - mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE); /* Initialize data structures */ mLastBssid = null; mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID; mLastSignalLevel = -1; - mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand()); + mWifiInfo.setMacAddress(mWifiNative.getMacAddress()); - WifiConfigStore.initialize(mContext); + mWifiConfigStore.initialize(); sendSupplicantConnectionChangedBroadcast(true); transitionTo(mDriverStartedState); @@ -2224,7 +2232,7 @@ public class WifiStateMachine extends StateMachine { case WifiMonitor.SUP_DISCONNECTION_EVENT: if (++mSupplicantRestartCount <= SUPPLICANT_RESTART_TRIES) { loge("Failed to setup control channel, restart supplicant"); - WifiNative.killSupplicant(); + mWifiNative.killSupplicant(); transitionTo(mDriverLoadedState); sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS); } else { @@ -2245,7 +2253,6 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2255,7 +2262,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2273,72 +2279,74 @@ public class WifiStateMachine extends StateMachine { long supplicantScanIntervalMs = Settings.Secure.getLong(mContext.getContentResolver(), Settings.Secure.WIFI_SUPPLICANT_SCAN_INTERVAL_MS, mDefaultSupplicantScanIntervalMs); - WifiNative.setScanIntervalCommand((int)supplicantScanIntervalMs / 1000); + mWifiNative.setScanInterval((int)supplicantScanIntervalMs / 1000); } @Override public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString() + "\n"); WifiConfiguration config; - boolean eventLoggingEnabled = true; switch(message.what) { case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */ transitionTo(mSupplicantStoppingState); break; case WifiMonitor.SUP_DISCONNECTION_EVENT: /* Supplicant connection lost */ loge("Connection lost, restart supplicant"); - WifiNative.killSupplicant(); - WifiNative.closeSupplicantConnection(); + mWifiNative.killSupplicant(); + mWifiNative.closeSupplicantConnection(); mNetworkInfo.setIsAvailable(false); handleNetworkDisconnect(); sendSupplicantConnectionChangedBroadcast(false); mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE); - mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE); transitionTo(mDriverLoadedState); sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS); break; case WifiMonitor.SCAN_RESULTS_EVENT: - eventLoggingEnabled = false; - setScanResults(WifiNative.scanResultsCommand()); + setScanResults(mWifiNative.scanResults()); sendScanResultsAvailableBroadcast(); mScanResultIsPending = false; break; case CMD_PING_SUPPLICANT: - boolean ok = WifiNative.pingCommand(); - mReplyChannel.replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); + boolean ok = mWifiNative.ping(); + replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); break; case CMD_ADD_OR_UPDATE_NETWORK: config = (WifiConfiguration) message.obj; - mReplyChannel.replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK, - WifiConfigStore.addOrUpdateNetwork(config)); + replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK, + mWifiConfigStore.addOrUpdateNetwork(config)); break; case CMD_REMOVE_NETWORK: - ok = WifiConfigStore.removeNetwork(message.arg1); - mReplyChannel.replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); + ok = mWifiConfigStore.removeNetwork(message.arg1); + replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); break; case CMD_ENABLE_NETWORK: - ok = WifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1); - mReplyChannel.replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); + ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1); + replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); break; case CMD_ENABLE_ALL_NETWORKS: long time = android.os.SystemClock.elapsedRealtime(); if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) { - WifiConfigStore.enableAllNetworks(); + mWifiConfigStore.enableAllNetworks(); mLastEnableAllNetworksTime = time; } break; - case CMD_DISABLE_NETWORK: - ok = WifiConfigStore.disableNetwork(message.arg1, message.arg2); - mReplyChannel.replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); + case WifiManager.DISABLE_NETWORK: + if (mWifiConfigStore.disableNetwork(message.arg1, + WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) { + replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED); + } else { + replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED, + WifiManager.ERROR); + } break; case CMD_BLACKLIST_NETWORK: - WifiNative.addToBlacklistCommand((String)message.obj); + mWifiNative.addToBlacklist((String)message.obj); break; case CMD_CLEAR_BLACKLIST: - WifiNative.clearBlacklistCommand(); + mWifiNative.clearBlacklist(); break; case CMD_SAVE_CONFIG: - ok = WifiConfigStore.saveConfig(); - mReplyChannel.replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE); + ok = mWifiConfigStore.saveConfig(); + replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE); // Inform the backup manager about a data change IBackupManager ibm = IBackupManager.Stub.asInterface( @@ -2359,19 +2367,29 @@ public class WifiStateMachine extends StateMachine { case CMD_SET_SCAN_MODE: mIsScanMode = (message.arg1 == SCAN_ONLY_MODE); break; - case CMD_SAVE_NETWORK: + case WifiManager.SAVE_NETWORK: config = (WifiConfiguration) message.obj; - WifiConfigStore.saveNetwork(config); + NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config); + if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) { + replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED); + } else { + loge("Failed to save network"); + replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, + WifiManager.ERROR); + } break; - case CMD_FORGET_NETWORK: - WifiConfigStore.forgetNetwork(message.arg1); + case WifiManager.FORGET_NETWORK: + if (mWifiConfigStore.forgetNetwork(message.arg1)) { + replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED); + } else { + loge("Failed to forget network"); + replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED, + WifiManager.ERROR); + } break; default: return NOT_HANDLED; } - if (eventLoggingEnabled) { - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - } return HANDLED; } @@ -2386,8 +2404,12 @@ public class WifiStateMachine extends StateMachine { public void enter() { if (DBG) log(getName() + "\n"); EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + /* Send any reset commands to supplicant before shutting it down */ + handleNetworkDisconnect(); + if (DBG) log("stopping supplicant"); - if (!WifiNative.stopSupplicant()) { + if (!mWifiNative.stopSupplicant()) { loge("Failed to stop supplicant"); } @@ -2396,11 +2418,9 @@ public class WifiStateMachine extends StateMachine { ++mSupplicantStopFailureToken, 0), SUPPLICANT_RESTART_INTERVAL_MSECS); mNetworkInfo.setIsAvailable(false); - handleNetworkDisconnect(); setWifiState(WIFI_STATE_DISABLING); sendSupplicantConnectionChangedBroadcast(false); mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE); - mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE); } @Override public boolean processMessage(Message message) { @@ -2414,15 +2434,15 @@ public class WifiStateMachine extends StateMachine { /* Socket connection can be lost when we do a graceful shutdown * or when the driver is hung. Ensure supplicant is stopped here. */ - WifiNative.killSupplicant(); - WifiNative.closeSupplicantConnection(); + mWifiNative.killSupplicant(); + mWifiNative.closeSupplicantConnection(); transitionTo(mDriverLoadedState); break; case CMD_STOP_SUPPLICANT_FAILED: if (message.arg1 == mSupplicantStopFailureToken) { loge("Timed out on a supplicant stop, kill and proceed"); - WifiNative.killSupplicant(); - WifiNative.closeSupplicantConnection(); + mWifiNative.killSupplicant(); + mWifiNative.closeSupplicantConnection(); transitionTo(mDriverLoadedState); } break; @@ -2436,7 +2456,6 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2446,7 +2465,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2479,7 +2497,6 @@ public class WifiStateMachine extends StateMachine { case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: case WifiMonitor.WPS_OVERLAP_EVENT: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2493,7 +2510,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2513,7 +2529,7 @@ public class WifiStateMachine extends StateMachine { * When this mode is on, some of the low-level scan parameters used by the * driver are changed to reduce interference with bluetooth */ - WifiNative.setBluetoothCoexistenceScanModeCommand(mBluetoothConnectionActive); + mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive); /* set country code */ setCountryCode(); /* set frequency band of operation */ @@ -2522,56 +2538,63 @@ public class WifiStateMachine extends StateMachine { setNetworkDetailedState(DetailedState.DISCONNECTED); /* Remove any filtering on Multicast v6 at start */ - WifiNative.stopFilteringMulticastV6Packets(); + mWifiNative.stopFilteringMulticastV6Packets(); /* Reset Multicast v4 filtering state */ if (mFilteringMulticastV4Packets.get()) { - WifiNative.startFilteringMulticastV4Packets(); + mWifiNative.startFilteringMulticastV4Packets(); } else { - WifiNative.stopFilteringMulticastV4Packets(); + mWifiNative.stopFilteringMulticastV4Packets(); } if (mIsScanMode) { - WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); - WifiNative.disconnectCommand(); + mWifiNative.setScanResultHandling(SCAN_ONLY_MODE); + mWifiNative.disconnect(); transitionTo(mScanModeState); } else { - WifiNative.setScanResultHandlingCommand(CONNECT_MODE); - WifiNative.reconnectCommand(); + mWifiNative.setScanResultHandling(CONNECT_MODE); + mWifiNative.reconnect(); + // Status pulls in the current supplicant state and network connection state + // events over the monitor connection. This helps framework sync up with + // current supplicant state + mWifiNative.status(); transitionTo(mDisconnectedState); } + + if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P); + + mContext.registerReceiver(mScreenReceiver, mScreenFilter); } @Override public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString() + "\n"); - boolean eventLoggingEnabled = true; switch(message.what) { - case CMD_SET_SCAN_TYPE: - if (message.arg1 == SCAN_ACTIVE) { - WifiNative.setScanModeCommand(true); - } else { - WifiNative.setScanModeCommand(false); - } + case CMD_SET_SCAN_TYPE: + mSetScanActive = (message.arg1 == SCAN_ACTIVE); + mWifiNative.setScanMode(mSetScanActive); break; case CMD_START_SCAN: - eventLoggingEnabled = false; - WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); + boolean forceActive = (message.arg1 == SCAN_ACTIVE); + if (forceActive && !mSetScanActive) { + mWifiNative.setScanMode(forceActive); + } + mWifiNative.scan(); + if (forceActive && !mSetScanActive) { + mWifiNative.setScanMode(mSetScanActive); + } mScanResultIsPending = true; break; - case CMD_SET_HIGH_PERF_MODE: - setHighPerfModeEnabledNative(message.arg1 == 1); - break; case CMD_SET_COUNTRY_CODE: String country = (String) message.obj; if (DBG) log("set country code " + country); - if (!WifiNative.setCountryCodeCommand(country.toUpperCase())) { + if (!mWifiNative.setCountryCode(country.toUpperCase())) { loge("Failed to set country code " + country); } break; case CMD_SET_FREQUENCY_BAND: int band = message.arg1; if (DBG) log("set frequency band " + band); - if (WifiNative.setBandCommand(band)) { + if (mWifiNative.setBand(band)) { mFrequencyBand.set(band); //Fetch the latest scan results when frequency band is set startScan(true); @@ -2582,7 +2605,7 @@ public class WifiStateMachine extends StateMachine { case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE: mBluetoothConnectionActive = (message.arg1 != BluetoothAdapter.STATE_DISCONNECTED); - WifiNative.setBluetoothCoexistenceScanModeCommand(mBluetoothConnectionActive); + mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive); break; case CMD_STOP_DRIVER: int mode = message.arg1; @@ -2615,38 +2638,51 @@ public class WifiStateMachine extends StateMachine { case CMD_DELAYED_STOP_DRIVER: if (message.arg1 != mDelayedStopCounter) break; if (getCurrentState() != mDisconnectedState) { - WifiNative.disconnectCommand(); + mWifiNative.disconnect(); handleNetworkDisconnect(); } mWakeLock.acquire(); - WifiNative.stopDriverCommand(); + mWifiNative.stopDriver(); transitionTo(mDriverStoppingState); mWakeLock.release(); break; case CMD_START_PACKET_FILTERING: if (message.arg1 == MULTICAST_V6) { - WifiNative.startFilteringMulticastV6Packets(); + mWifiNative.startFilteringMulticastV6Packets(); } else if (message.arg1 == MULTICAST_V4) { - WifiNative.startFilteringMulticastV4Packets(); + mWifiNative.startFilteringMulticastV4Packets(); } else { loge("Illegal arugments to CMD_START_PACKET_FILTERING"); } break; case CMD_STOP_PACKET_FILTERING: if (message.arg1 == MULTICAST_V6) { - WifiNative.stopFilteringMulticastV6Packets(); + mWifiNative.stopFilteringMulticastV6Packets(); } else if (message.arg1 == MULTICAST_V4) { - WifiNative.stopFilteringMulticastV4Packets(); + mWifiNative.stopFilteringMulticastV4Packets(); } else { loge("Illegal arugments to CMD_STOP_PACKET_FILTERING"); } break; + case CMD_SET_SUSPEND_OPTIMIZATIONS: + if (!mHighPerfMode) { + mWifiNative.setSuspendOptimizations(true); + } + mSuspendWakeLock.release(); + break; + case CMD_CLEAR_SUSPEND_OPTIMIZATIONS: + mWifiNative.setSuspendOptimizations(false); + break; + case CMD_SET_HIGH_PERF_MODE: + mHighPerfMode = (message.arg1 == 1); + if (mHighPerfMode) { + //Disable any suspend optimizations + mWifiNative.setSuspendOptimizations(false); + } + break; default: return NOT_HANDLED; } - if (eventLoggingEnabled) { - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - } return HANDLED; } @Override @@ -2655,6 +2691,9 @@ public class WifiStateMachine extends StateMachine { mIsRunning = false; updateBatteryWorkSource(null); mScanResults = null; + + if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P); + mContext.unregisterReceiver(mScreenReceiver); } } @@ -2678,7 +2717,6 @@ public class WifiStateMachine extends StateMachine { case CMD_START_DRIVER: case CMD_STOP_DRIVER: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2692,7 +2730,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2718,14 +2755,13 @@ public class WifiStateMachine extends StateMachine { break; case CMD_START_DRIVER: mWakeLock.acquire(); - WifiNative.startDriverCommand(); + mWifiNative.startDriver(); mWakeLock.release(); transitionTo(mDriverStartingState); break; default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2745,8 +2781,8 @@ public class WifiStateMachine extends StateMachine { /* Ignore */ return HANDLED; } else { - WifiNative.setScanResultHandlingCommand(message.arg1); - WifiNative.reconnectCommand(); + mWifiNative.setScanResultHandling(message.arg1); + mWifiNative.reconnect(); mIsScanMode = false; transitionTo(mDisconnectedState); } @@ -2762,7 +2798,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -2781,10 +2816,6 @@ public class WifiStateMachine extends StateMachine { case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT); break; - case WifiMonitor.WPS_OVERLAP_EVENT: - /* We just need to broadcast the error */ - sendErrorBroadcast(WifiManager.WPS_OVERLAP_ERROR); - break; case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: SupplicantState state = handleSupplicantStateChange(message); // Due to a WEXT bug, during the time of driver start/stop @@ -2813,15 +2844,15 @@ public class WifiStateMachine extends StateMachine { break; /* Do a redundant disconnect without transition */ case CMD_DISCONNECT: - WifiNative.disconnectCommand(); + mWifiNative.disconnect(); break; case CMD_RECONNECT: - WifiNative.reconnectCommand(); + mWifiNative.reconnect(); break; case CMD_REASSOCIATE: - WifiNative.reassociateCommand(); + mWifiNative.reassociate(); break; - case CMD_CONNECT_NETWORK: + case WifiManager.CONNECT_NETWORK: int netId = message.arg1; WifiConfiguration config = (WifiConfiguration) message.obj; @@ -2833,29 +2864,54 @@ public class WifiStateMachine extends StateMachine { * a connection to the enabled network. */ if (config != null) { - netId = WifiConfigStore.selectNetwork(config); + netId = mWifiConfigStore.selectNetwork(config); } else { - WifiConfigStore.selectNetwork(netId); + mWifiConfigStore.selectNetwork(netId); } /* The state tracker handles enabling networks upon completion/failure */ - mSupplicantStateTracker.sendMessage(CMD_CONNECT_NETWORK); + mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK); + + if (mWifiNative.reconnect()) { + replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED); + } else { + loge("Failed to initiate connection"); + replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED, + WifiManager.ERROR); + } - WifiNative.reconnectCommand(); - mLastExplicitNetworkId = netId; - mLastNetworkChoiceTime = SystemClock.elapsedRealtime(); - mNextWifiActionExplicit = true; - if (DBG) log("Setting wifi connect explicit for netid " + netId); /* Expect a disconnection from the old connection */ transitionTo(mDisconnectingState); break; - case CMD_START_WPS: - mWpsStateMachine.sendMessage(Message.obtain(message)); - transitionTo(mWaitForWpsCompletionState); + case WifiManager.START_WPS: + WpsInfo wpsInfo = (WpsInfo) message.obj; + WpsResult result; + switch (wpsInfo.setup) { + case WpsInfo.PBC: + result = mWifiConfigStore.startWpsPbc(wpsInfo); + break; + case WpsInfo.KEYPAD: + result = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo); + break; + case WpsInfo.DISPLAY: + result = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo); + break; + default: + result = new WpsResult(Status.FAILURE); + Log.e(TAG, "Invalid setup for WPS"); + break; + } + if (result.status == Status.SUCCESS) { + replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, result); + transitionTo(mWpsRunningState); + } else { + Log.e(TAG, "Failed to start WPS with config " + wpsInfo.toString()); + replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR); + } break; case WifiMonitor.SCAN_RESULTS_EVENT: /* Set the scan setting back to "connect" mode */ - WifiNative.setScanResultHandlingCommand(CONNECT_MODE); + mWifiNative.setScanResultHandling(CONNECT_MODE); /* Handle scan results */ return NOT_HANDLED; case WifiMonitor.NETWORK_CONNECTION_EVENT: @@ -2863,21 +2919,12 @@ public class WifiStateMachine extends StateMachine { mLastNetworkId = message.arg1; mLastBssid = (String) message.obj; - //TODO: make supplicant modification to push this in events - mWifiInfo.setSSID(fetchSSID()); mWifiInfo.setBSSID(mLastBssid); mWifiInfo.setNetworkId(mLastNetworkId); - if (mNextWifiActionExplicit && - mWifiInfo.getNetworkId() == mLastExplicitNetworkId && - SystemClock.elapsedRealtime() < mLastNetworkChoiceTime + - EXPLICIT_CONNECT_ALLOWED_DELAY_MS) { - mWifiInfo.setExplicitConnect(true); - } - mNextWifiActionExplicit = false; /* send event to CM & network change broadcast */ setNetworkDetailedState(DetailedState.OBTAINING_IPADDR); sendNetworkStateChangeBroadcast(mLastBssid); - transitionTo(mConnectingState); + transitionTo(mObtainingIpState); break; case WifiMonitor.NETWORK_DISCONNECTION_EVENT: if (DBG) log("Network connection lost"); @@ -2887,131 +2934,24 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } - class ConnectingState extends State { - - @Override - public void enter() { - if (DBG) log(getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - try { - mNwService.enableIpv6(mInterfaceName); - } catch (RemoteException re) { - loge("Failed to enable IPv6: " + re); - } catch (IllegalStateException e) { - loge("Failed to enable IPv6: " + e); - } - - if (!WifiConfigStore.isUsingStaticIp(mLastNetworkId)) { - //start DHCP - mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine( - mContext, WifiStateMachine.this, mInterfaceName); - mDhcpStateMachine.registerForPreDhcpNotification(); - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); - } else { - DhcpInfoInternal dhcpInfoInternal = WifiConfigStore.getIpConfiguration( - mLastNetworkId); - InterfaceConfiguration ifcg = new InterfaceConfiguration(); - ifcg.addr = dhcpInfoInternal.makeLinkAddress(); - ifcg.interfaceFlags = "[up]"; - try { - mNwService.setInterfaceConfig(mInterfaceName, ifcg); - if (DBG) log("Static IP configuration succeeded"); - sendMessage(CMD_STATIC_IP_SUCCESS, dhcpInfoInternal); - } catch (RemoteException re) { - loge("Static IP configuration failed: " + re); - sendMessage(CMD_STATIC_IP_FAILURE); - } catch (IllegalStateException e) { - loge("Static IP configuration failed: " + e); - sendMessage(CMD_STATIC_IP_FAILURE); - } - } - } - @Override - public boolean processMessage(Message message) { - if (DBG) log(getName() + message.toString() + "\n"); - - switch(message.what) { - case DhcpStateMachine.CMD_PRE_DHCP_ACTION: - handlePreDhcpSetup(); - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE); - break; - case DhcpStateMachine.CMD_POST_DHCP_ACTION: - handlePostDhcpSetup(); - if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) { - handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); - transitionTo(mConnectedState); - } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) { - handleFailedIpConfiguration(); - transitionTo(mDisconnectingState); - } - break; - case CMD_STATIC_IP_SUCCESS: - handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); - transitionTo(mConnectedState); - break; - case CMD_STATIC_IP_FAILURE: - handleFailedIpConfiguration(); - transitionTo(mDisconnectingState); - break; - case CMD_DISCONNECT: - WifiNative.disconnectCommand(); - transitionTo(mDisconnectingState); - break; - /* Ignore connection to same network */ - case CMD_CONNECT_NETWORK: - int netId = message.arg1; - if (mWifiInfo.getNetworkId() == netId) { - break; - } - return NOT_HANDLED; - case CMD_SAVE_NETWORK: - deferMessage(message); - break; - /* Ignore */ - case WifiMonitor.NETWORK_CONNECTION_EVENT: - break; - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - sendMessage(CMD_DISCONNECT); - deferMessage(message); - } - break; - /* Defer scan when IP is being fetched */ - case CMD_START_SCAN: - deferMessage(message); - break; - /* Defer any power mode changes since we must keep active power mode at DHCP */ - case CMD_SET_HIGH_PERF_MODE: - deferMessage(message); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class ConnectedState extends State { + class L2ConnectedState extends State { @Override public void enter() { if (DBG) log(getName() + "\n"); EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); mRssiPollToken++; if (mEnableRssiPolling) { - sendMessage(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, mRssiPollToken, 0)); + sendMessage(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0)); } } + @Override public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString() + "\n"); - boolean eventLoggingEnabled = true; switch (message.what) { case DhcpStateMachine.CMD_PRE_DHCP_ACTION: handlePreDhcpSetup(); @@ -3020,14 +2960,17 @@ public class WifiStateMachine extends StateMachine { case DhcpStateMachine.CMD_POST_DHCP_ACTION: handlePostDhcpSetup(); if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) { + if (DBG) log("DHCP successful"); handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); + transitionTo(mVerifyingLinkState); } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) { + if (DBG) log("DHCP failed"); handleFailedIpConfiguration(); transitionTo(mDisconnectingState); } break; case CMD_DISCONNECT: - WifiNative.disconnectCommand(); + mWifiNative.disconnect(); transitionTo(mDisconnectingState); break; case CMD_SET_SCAN_MODE: @@ -3037,29 +2980,28 @@ public class WifiStateMachine extends StateMachine { } break; case CMD_START_SCAN: - eventLoggingEnabled = false; /* When the network is connected, re-scanning can trigger * a reconnection. Put it in scan-only mode during scan. * When scan results are received, the mode is switched * back to CONNECT_MODE. */ - WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); + mWifiNative.setScanResultHandling(SCAN_ONLY_MODE); /* Have the parent state handle the rest */ return NOT_HANDLED; /* Ignore connection to same network */ - case CMD_CONNECT_NETWORK: + case WifiManager.CONNECT_NETWORK: int netId = message.arg1; if (mWifiInfo.getNetworkId() == netId) { break; } return NOT_HANDLED; - case CMD_SAVE_NETWORK: + case WifiManager.SAVE_NETWORK: WifiConfiguration config = (WifiConfiguration) message.obj; - NetworkUpdateResult result = WifiConfigStore.saveNetwork(config); + NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config); if (mWifiInfo.getNetworkId() == result.getNetworkId()) { if (result.hasIpChanged()) { log("Reconfiguring IP on connection"); - transitionTo(mConnectingState); + transitionTo(mObtainingIpState); } if (result.hasProxyChanged()) { log("Reconfiguring proxy on connection"); @@ -3067,16 +3009,23 @@ public class WifiStateMachine extends StateMachine { sendLinkConfigurationChangedBroadcast(); } } + + if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) { + replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED); + } else { + loge("Failed to save network"); + replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, + WifiManager.ERROR); + } break; /* Ignore */ case WifiMonitor.NETWORK_CONNECTION_EVENT: break; case CMD_RSSI_POLL: - eventLoggingEnabled = false; if (message.arg1 == mRssiPollToken) { // Get Info and continue polling fetchRssiAndLinkSpeedNative(); - sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, + sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); } else { // Polling has completed @@ -3088,34 +3037,165 @@ public class WifiStateMachine extends StateMachine { if (mEnableRssiPolling) { // first poll fetchRssiAndLinkSpeedNative(); - sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, + sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); } break; + case WifiWatchdogStateMachine.RSSI_FETCH: + fetchRssiAndLinkSpeedNative(); + replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_SUCCEEDED, + mWifiInfo.getRssi()); + break; default: return NOT_HANDLED; } - if (eventLoggingEnabled) { - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - } + return HANDLED; } + @Override public void exit() { - - /* Request a CS wakelock during transition to mobile */ - checkAndSetConnectivityInstance(); - mCm.requestNetworkTransitionWakelock(TAG); - /* If a scan result is pending in connected state, the supplicant * is in SCAN_ONLY_MODE. Restore CONNECT_MODE on exit */ if (mScanResultIsPending) { - WifiNative.setScanResultHandlingCommand(CONNECT_MODE); + mWifiNative.setScanResultHandling(CONNECT_MODE); } } } + class ObtainingIpState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { + //start DHCP + mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine( + mContext, WifiStateMachine.this, mInterfaceName); + mDhcpStateMachine.registerForPreDhcpNotification(); + mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); + } else { + DhcpInfoInternal dhcpInfoInternal = mWifiConfigStore.getIpConfiguration( + mLastNetworkId); + InterfaceConfiguration ifcg = new InterfaceConfiguration(); + ifcg.setLinkAddress(dhcpInfoInternal.makeLinkAddress()); + ifcg.setInterfaceUp(); + try { + mNwService.setInterfaceConfig(mInterfaceName, ifcg); + if (DBG) log("Static IP configuration succeeded"); + sendMessage(CMD_STATIC_IP_SUCCESS, dhcpInfoInternal); + } catch (RemoteException re) { + loge("Static IP configuration failed: " + re); + sendMessage(CMD_STATIC_IP_FAILURE); + } catch (IllegalStateException e) { + loge("Static IP configuration failed: " + e); + sendMessage(CMD_STATIC_IP_FAILURE); + } + } + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString() + "\n"); + switch(message.what) { + case CMD_STATIC_IP_SUCCESS: + handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj); + transitionTo(mVerifyingLinkState); + break; + case CMD_STATIC_IP_FAILURE: + handleFailedIpConfiguration(); + transitionTo(mDisconnectingState); + break; + case WifiManager.SAVE_NETWORK: + deferMessage(message); + break; + /* Defer any power mode changes since we must keep active power mode at DHCP */ + case CMD_SET_HIGH_PERF_MODE: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class VerifyingLinkState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK); + sendNetworkStateChangeBroadcast(mLastBssid); + } + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case WifiWatchdogStateMachine.POOR_LINK_DETECTED: + //stay here + break; + case WifiWatchdogStateMachine.GOOD_LINK_DETECTED: + try { + mNwService.enableIpv6(mInterfaceName); + } catch (RemoteException re) { + loge("Failed to enable IPv6: " + re); + } catch (IllegalStateException e) { + loge("Failed to enable IPv6: " + e); + } + + setNetworkDetailedState(DetailedState.CONNECTED); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED); + sendNetworkStateChangeBroadcast(mLastBssid); + transitionTo(mConnectedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class ConnectedState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString() + "\n"); + switch (message.what) { + case WifiWatchdogStateMachine.POOR_LINK_DETECTED: + if (DBG) log("Watchdog reports poor link"); + try { + mNwService.disableIpv6(mInterfaceName); + } catch (RemoteException re) { + loge("Failed to disable IPv6: " + re); + } catch (IllegalStateException e) { + loge("Failed to disable IPv6: " + e); + } + /* Report a disconnect */ + setNetworkDetailedState(DetailedState.DISCONNECTED); + mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED); + sendNetworkStateChangeBroadcast(mLastBssid); + + transitionTo(mVerifyingLinkState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + @Override + public void exit() { + /* Request a CS wakelock during transition to mobile */ + checkAndSetConnectivityInstance(); + mCm.requestNetworkTransitionWakelock(TAG); + } + } + class DisconnectingState extends State { @Override public void enter() { @@ -3143,7 +3223,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -3192,7 +3271,7 @@ public class WifiStateMachine extends StateMachine { * cleared */ if (!mScanResultIsPending) { - WifiNative.enableBackgroundScanCommand(true); + mWifiNative.enableBackgroundScan(true); } } else { setScanAlarm(true); @@ -3204,9 +3283,9 @@ public class WifiStateMachine extends StateMachine { switch (message.what) { case CMD_SET_SCAN_MODE: if (message.arg1 == SCAN_ONLY_MODE) { - WifiNative.setScanResultHandlingCommand(message.arg1); + mWifiNative.setScanResultHandling(message.arg1); //Supplicant disconnect to prevent further connects - WifiNative.disconnectCommand(); + mWifiNative.disconnect(); mIsScanMode = true; transitionTo(mScanModeState); } @@ -3214,10 +3293,10 @@ public class WifiStateMachine extends StateMachine { case CMD_ENABLE_BACKGROUND_SCAN: mEnableBackgroundScan = (message.arg1 == 1); if (mEnableBackgroundScan) { - WifiNative.enableBackgroundScanCommand(true); + mWifiNative.enableBackgroundScan(true); setScanAlarm(false); } else { - WifiNative.enableBackgroundScanCommand(false); + mWifiNative.enableBackgroundScan(false); setScanAlarm(true); } break; @@ -3232,21 +3311,20 @@ public class WifiStateMachine extends StateMachine { case CMD_START_SCAN: /* Disable background scan temporarily during a regular scan */ if (mEnableBackgroundScan) { - WifiNative.enableBackgroundScanCommand(false); + mWifiNative.enableBackgroundScan(false); } /* Handled in parent state */ return NOT_HANDLED; case WifiMonitor.SCAN_RESULTS_EVENT: /* Re-enable background scan when a pending scan result is received */ if (mEnableBackgroundScan && mScanResultIsPending) { - WifiNative.enableBackgroundScanCommand(true); + mWifiNative.enableBackgroundScan(true); } /* Handled in parent state */ return NOT_HANDLED; default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } @@ -3254,49 +3332,95 @@ public class WifiStateMachine extends StateMachine { public void exit() { /* No need for a background scan upon exit from a disconnected state */ if (mEnableBackgroundScan) { - WifiNative.enableBackgroundScanCommand(false); + mWifiNative.enableBackgroundScan(false); } setScanAlarm(false); } } - class WaitForWpsCompletionState extends State { + class WpsRunningState extends State { + //Tracks the source to provide a reply + private Message mSourceMessage; @Override public void enter() { if (DBG) log(getName() + "\n"); EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + mSourceMessage = Message.obtain(getCurrentMessage()); } @Override public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString() + "\n"); switch (message.what) { + case WifiMonitor.WPS_SUCCESS_EVENT: + replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED); + mSourceMessage.recycle(); + mSourceMessage = null; + transitionTo(mDisconnectedState); + break; + case WifiMonitor.WPS_OVERLAP_EVENT: + replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, + WifiManager.WPS_OVERLAP_ERROR); + mSourceMessage.recycle(); + mSourceMessage = null; + transitionTo(mDisconnectedState); + break; + case WifiMonitor.WPS_FAIL_EVENT: + //arg1 has the reason for the failure + replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, message.arg1); + mSourceMessage.recycle(); + mSourceMessage = null; + transitionTo(mDisconnectedState); + break; + case WifiMonitor.WPS_TIMEOUT_EVENT: + replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, + WifiManager.WPS_TIMED_OUT); + mSourceMessage.recycle(); + mSourceMessage = null; + transitionTo(mDisconnectedState); + break; + case WifiManager.START_WPS: + replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.IN_PROGRESS); + break; + case WifiManager.CANCEL_WPS: + if (mWifiNative.cancelWps()) { + replyToMessage(message, WifiManager.CANCEL_WPS_SUCCEDED); + } else { + replyToMessage(message, WifiManager.CANCEL_WPS_FAILED, WifiManager.ERROR); + } + transitionTo(mDisconnectedState); + break; /* Defer all commands that can cause connections to a different network * or put the state machine out of connect mode */ case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: - case CMD_CONNECT_NETWORK: + case WifiManager.CONNECT_NETWORK: case CMD_ENABLE_NETWORK: case CMD_RECONNECT: case CMD_REASSOCIATE: - case WifiMonitor.NETWORK_CONNECTION_EVENT: /* Handled after IP & proxy update */ + case WifiMonitor.NETWORK_CONNECTION_EVENT: /* Handled after exiting WPS state */ deferMessage(message); break; case WifiMonitor.NETWORK_DISCONNECTION_EVENT: if (DBG) log("Network connection lost"); handleNetworkDisconnect(); break; - case WPS_COMPLETED_EVENT: - /* we are still disconnected until we see a network connection - * notification */ - transitionTo(mDisconnectedState); + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: + //Throw away supplicant state changes when WPS is running. + //We will start getting supplicant state changes once we get + //a WPS success or failure break; default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } + + @Override + public void exit() { + mWifiConfigStore.enableAllNetworks(); + mWifiConfigStore.loadConfiguredNetworks(); + } } class SoftApStartingState extends State { @@ -3333,13 +3457,11 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: case CMD_STOP_PACKET_FILTERING: case CMD_TETHER_STATE_CHANGE: - case WifiP2pService.P2P_ENABLE_PENDING: deferMessage(message); break; case WifiStateMachine.CMD_RESPONSE_AP_CONFIG: @@ -3362,7 +3484,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -3403,59 +3524,9 @@ public class WifiStateMachine extends StateMachine { transitionTo(mTetheringState); } break; - case WifiP2pService.P2P_ENABLE_PENDING: - // turn of soft Ap and defer to be handled in DriverUnloadedState - setWifiApEnabled(null, false); - deferMessage(message); - break; default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class WaitForP2pDisableState extends State { - private int mSavedArg; - @Override - public void enter() { - if (DBG) log(getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - //Preserve the argument arg1 that has information used in DriverLoadingState - mSavedArg = getCurrentMessage().arg1; - } - @Override - public boolean processMessage(Message message) { - if (DBG) log(getName() + message.toString() + "\n"); - switch(message.what) { - case WifiP2pService.WIFI_ENABLE_PROCEED: - //restore argument from original message (CMD_LOAD_DRIVER) - message.arg1 = mSavedArg; - transitionTo(mDriverLoadingState); - break; - case CMD_LOAD_DRIVER: - case CMD_UNLOAD_DRIVER: - case CMD_START_SUPPLICANT: - case CMD_STOP_SUPPLICANT: - case CMD_START_AP: - case CMD_STOP_AP: - case CMD_START_DRIVER: - case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: - case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: - case CMD_SET_COUNTRY_CODE: - case CMD_SET_FREQUENCY_BAND: - case CMD_START_PACKET_FILTERING: - case CMD_STOP_PACKET_FILTERING: - deferMessage(message); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -3496,18 +3567,15 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: case CMD_STOP_PACKET_FILTERING: - case WifiP2pService.P2P_ENABLE_PENDING: deferMessage(message); break; default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -3538,7 +3606,6 @@ public class WifiStateMachine extends StateMachine { default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } @@ -3592,22 +3659,56 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_SET_SCAN_MODE: case CMD_SET_SCAN_TYPE: - case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: case CMD_STOP_PACKET_FILTERING: - case WifiP2pService.P2P_ENABLE_PENDING: deferMessage(message); break; default: return NOT_HANDLED; } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); return HANDLED; } } + //State machine initiated requests can have replyTo set to null indicating + //there are no recepients, we ignore those reply actions + private void replyToMessage(Message msg, int what) { + if (msg.replyTo == null) return; + Message dstMsg = obtainMessageWithArg2(msg); + dstMsg.what = what; + mReplyChannel.replyToMessage(msg, dstMsg); + } + + private void replyToMessage(Message msg, int what, int arg1) { + if (msg.replyTo == null) return; + Message dstMsg = obtainMessageWithArg2(msg); + dstMsg.what = what; + dstMsg.arg1 = arg1; + mReplyChannel.replyToMessage(msg, dstMsg); + } + + private void replyToMessage(Message msg, int what, Object obj) { + if (msg.replyTo == null) return; + Message dstMsg = obtainMessageWithArg2(msg); + dstMsg.what = what; + dstMsg.obj = obj; + mReplyChannel.replyToMessage(msg, dstMsg); + } + + /** + * arg2 on the source message has a unique id that needs to be retained in replies + * to match the request + * + * see WifiManager for details + */ + private Message obtainMessageWithArg2(Message srcMsg) { + Message msg = Message.obtain(); + msg.arg2 = srcMsg.arg2; + return msg; + } + private void log(String s) { Log.d(TAG, s); } diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java index b27c60f..f31ee68 100644 --- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -26,9 +26,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.database.ContentObserver; +import android.net.arp.ArpPeer; import android.net.ConnectivityManager; -import android.net.DnsPinger; +import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.NetworkInfo; +import android.net.RouteInfo; import android.net.Uri; import android.os.Message; import android.os.SystemClock; @@ -38,6 +41,7 @@ import android.provider.Settings.Secure; import android.util.Log; import com.android.internal.R; +import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -46,49 +50,88 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.InetAddress; +import java.net.SocketException; import java.net.URL; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; /** - * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi - * network with multiple access points. After the framework successfully - * connects to an access point, the watchdog verifies connectivity by 'pinging' - * the configured DNS server using {@link DnsPinger}. - * <p> - * On DNS check failure, the BSSID is blacklisted if it is reasonably likely - * that another AP might have internet access; otherwise the SSID is disabled. - * <p> - * On DNS success, the WatchdogService initiates a walled garden check via an - * http get. A browser window is activated if a walled garden is detected. + * WifiWatchdogStateMachine monitors the connection to a Wi-Fi + * network. After the framework notifies that it has connected to an + * acccess point and is waiting for link to be verified, the watchdog + * takes over and verifies if the link is good by doing ARP pings to + * the gateway using {@link ArpPeer}. + * + * Upon successful verification, the watchdog notifies and continues + * to monitor the link afterwards when the RSSI level falls below + * a certain threshold. + + * When Wi-fi connects at L2 layer, the beacons from access point reach + * the device and it can maintain a connection, but the application + * connectivity can be flaky (due to bigger packet size exchange). + * + * We now monitor the quality of the last hop on + * Wi-Fi using signal strength and ARP connectivity as indicators + * to decide if the link is good enough to switch to Wi-Fi as the uplink. + * + * ARP pings are useful for link validation but can still get through + * when the application traffic fails to go through and are thus not + * the best indicator of real packet loss since they are tiny packets + * (28 bytes) and have a much low chance of packet corruption than the + * regular data packets. + * + * When signal strength and ARP are used together, it ends up working well in tests. + * The goal is to switch to Wi-Fi after validating ARP transfer + * and RSSI and then switching out of Wi-Fi when we hit a low + * signal strength threshold and then waiting until the signal strength + * improves and validating ARP transfer. * * @hide */ public class WifiWatchdogStateMachine extends StateMachine { - private static final boolean DBG = false; + /* STOPSHIP: Keep this configurable for debugging until ship */ + private static boolean DBG = false; private static final String TAG = "WifiWatchdogStateMachine"; - private static final String DISABLED_NETWORK_NOTIFICATION_ID = "WifiWatchdog.networkdisabled"; private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden"; - private static final int WIFI_SIGNAL_LEVELS = 4; - /** - * Low signal is defined as less than or equal to cut off - */ - private static final int LOW_SIGNAL_CUTOFF = 0; - - private static final long DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS = 2 * 60 * 1000; - private static final long DEFAULT_DNS_CHECK_LONG_INTERVAL_MS = 60 * 60 * 1000; + /* RSSI Levels as used by notification icon + Level 4 -55 <= RSSI + Level 3 -66 <= RSSI < -55 + Level 2 -77 <= RSSI < -67 + Level 1 -88 <= RSSI < -78 + Level 0 RSSI < -88 */ + + /* Wi-fi connection is considered poor below this + RSSI level threshold and the watchdog report it + to the WifiStateMachine */ + private static final int RSSI_LEVEL_CUTOFF = 0; + /* Wi-fi connection is monitored actively below this + threshold */ + private static final int RSSI_LEVEL_MONITOR = 1; + /* RSSI threshold during monitoring below which network is avoided */ + private static final int RSSI_MONITOR_THRESHOLD = -84; + /* Number of times RSSI is measured to be low before being avoided */ + private static final int RSSI_MONITOR_COUNT = 5; + private int mRssiMonitorCount = 0; + + /* Avoid flapping. The interval is changed over time as long as we continue to avoid + * under the max interval after which we reset the interval again */ + private static final int MIN_INTERVAL_AVOID_BSSID_MS[] = {0, 30 * 1000, 60 * 1000, + 5 * 60 * 1000, 30 * 60 * 1000}; + /* Index into the interval array MIN_INTERVAL_AVOID_BSSID_MS */ + private int mMinIntervalArrayIndex = 0; + + private long mLastBssidAvoidedTime; + + private int mCurrentSignalLevel; + + private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000; + private static final long DEFAULT_RSSI_FETCH_INTERVAL_MS = 1000; private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; - private static final int DEFAULT_MAX_SSID_BLACKLISTS = 7; - private static final int DEFAULT_NUM_DNS_PINGS = 5; // Multiple pings to detect setup issues - private static final int DEFAULT_MIN_DNS_RESPONSES = 1; - - private static final int DEFAULT_DNS_PING_TIMEOUT_MS = 2000; + private static final int DEFAULT_NUM_ARP_PINGS = 5; + private static final int DEFAULT_MIN_ARP_RESPONSES = 1; - private static final long DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000; + private static final int DEFAULT_ARP_PING_TIMEOUT_MS = 100; // See http://go/clientsdns for usage approval private static final String DEFAULT_WALLED_GARDEN_URL = @@ -102,10 +145,6 @@ public class WifiWatchdogStateMachine extends StateMachine { */ private static final int WALLED_GARDEN_START_DELAY_MS = 3000; - private static final int DNS_INTRATEST_PING_INTERVAL_MS = 200; - /* With some router setups, it takes a few hunder milli-seconds before connection is active */ - private static final int DNS_START_DELAY_MS = 1000; - private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; /** @@ -118,99 +157,84 @@ public class WifiWatchdogStateMachine extends StateMachine { * which has a non-null networkInfo object */ private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; - /** - * Indicates the signal has changed. Passed with arg1 - * {@link #mNetEventCounter} and arg2 [raw signal strength] - */ + /* Passed with RSSI information */ private static final int EVENT_RSSI_CHANGE = BASE + 3; - private static final int EVENT_SCAN_RESULTS_AVAILABLE = BASE + 4; private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6; - private static final int MESSAGE_HANDLE_WALLED_GARDEN = BASE + 100; - private static final int MESSAGE_HANDLE_BAD_AP = BASE + 101; - /** - * arg1 == mOnlineWatchState.checkCount - */ - private static final int MESSAGE_SINGLE_DNS_CHECK = BASE + 102; - private static final int MESSAGE_NETWORK_FOLLOWUP = BASE + 103; - private static final int MESSAGE_DELAYED_WALLED_GARDEN_CHECK = BASE + 104; + /* Internal messages */ + private static final int CMD_ARP_CHECK = BASE + 11; + private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 12; + private static final int CMD_RSSI_FETCH = BASE + 13; + + /* Notifications to WifiStateMachine */ + static final int POOR_LINK_DETECTED = BASE + 21; + static final int GOOD_LINK_DETECTED = BASE + 22; + static final int RSSI_FETCH = BASE + 23; + static final int RSSI_FETCH_SUCCEEDED = BASE + 24; + static final int RSSI_FETCH_FAILED = BASE + 25; + + private static final int SINGLE_ARP_CHECK = 0; + private static final int FULL_ARP_CHECK = 1; private Context mContext; private ContentResolver mContentResolver; private WifiManager mWifiManager; - private DnsPinger mDnsPinger; private IntentFilter mIntentFilter; private BroadcastReceiver mBroadcastReceiver; + private AsyncChannel mWsmChannel = new AsyncChannel();; private DefaultState mDefaultState = new DefaultState(); private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState(); private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState(); private NotConnectedState mNotConnectedState = new NotConnectedState(); + private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState(); private ConnectedState mConnectedState = new ConnectedState(); - private DnsCheckingState mDnsCheckingState = new DnsCheckingState(); + private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState(); + /* Online and watching link connectivity */ private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); + /* RSSI level is at RSSI_LEVEL_MONITOR and needs close monitoring */ + private RssiMonitoringState mRssiMonitoringState = new RssiMonitoringState(); + /* Online and doing nothing */ private OnlineState mOnlineState = new OnlineState(); - private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState(); - private DelayWalledGardenState mDelayWalledGardenState = new DelayWalledGardenState(); - private WalledGardenState mWalledGardenState = new WalledGardenState(); - private BlacklistedApState mBlacklistedApState = new BlacklistedApState(); - private long mDnsCheckShortIntervalMs; - private long mDnsCheckLongIntervalMs; + private int mArpToken = 0; + private long mArpCheckIntervalMs; + private int mRssiFetchToken = 0; + private long mRssiFetchIntervalMs; private long mWalledGardenIntervalMs; - private int mMaxSsidBlacklists; - private int mNumDnsPings; - private int mMinDnsResponses; - private int mDnsPingTimeoutMs; - private long mBlacklistFollowupIntervalMs; + private int mNumArpPings; + private int mMinArpResponses; + private int mArpPingTimeoutMs; private boolean mPoorNetworkDetectionEnabled; private boolean mWalledGardenTestEnabled; private String mWalledGardenUrl; - private boolean mShowDisabledNotification; - /** - * The {@link WifiInfo} object passed to WWSM on network broadcasts - */ - private WifiInfo mConnectionInfo; - private int mNetEventCounter = 0; - - /** - * Currently maintained but not used, TODO - */ - private HashSet<String> mBssids = new HashSet<String>(); - private int mNumCheckFailures = 0; + private WifiInfo mWifiInfo; + private LinkProperties mLinkProperties; - private Long mLastWalledGardenCheckTime = null; + private long mLastWalledGardenCheckTime = 0; - /** - * This is set by the blacklisted state and reset when connected to a new AP. - * It triggers a disableNetwork call if a DNS check fails. - */ - public boolean mDisableAPNextFailure = false; private static boolean sWifiOnly = false; - private boolean mDisabledNotificationShown; private boolean mWalledGardenNotificationShown; - public boolean mHasConnectedWifiManager = false; /** * STATE MAP * Default * / \ - * Disabled Enabled - * / \ - * NotConnected Connected - * /---------\ - * (all other states) + * Disabled Enabled + * / \ \ + * NotConnected Verifying Connected + * /---------\ + * (all other states) */ private WifiWatchdogStateMachine(Context context) { super(TAG); mContext = context; mContentResolver = context.getContentResolver(); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - mDnsPinger = new DnsPinger(mContext, "WifiWatchdogStateMachine.DnsPinger", - this.getHandler().getLooper(), this.getHandler(), - ConnectivityManager.TYPE_WIFI); + mWsmChannel.connectSync(mContext, getHandler(), + mWifiManager.getWifiStateMachineMessenger()); setupNetworkReceiver(); @@ -221,16 +245,18 @@ public class WifiWatchdogStateMachine extends StateMachine { addState(mWatchdogDisabledState, mDefaultState); addState(mWatchdogEnabledState, mDefaultState); addState(mNotConnectedState, mWatchdogEnabledState); + addState(mVerifyingLinkState, mWatchdogEnabledState); addState(mConnectedState, mWatchdogEnabledState); - addState(mDnsCheckingState, mConnectedState); - addState(mDnsCheckFailureState, mConnectedState); - addState(mDelayWalledGardenState, mConnectedState); - addState(mWalledGardenState, mConnectedState); - addState(mBlacklistedApState, mConnectedState); + addState(mWalledGardenCheckState, mConnectedState); addState(mOnlineWatchState, mConnectedState); + addState(mRssiMonitoringState, mOnlineWatchState); addState(mOnlineState, mConnectedState); - setInitialState(mWatchdogDisabledState); + if (isWatchdogEnabled()) { + setInitialState(mNotConnectedState); + } else { + setInitialState(mWatchdogDisabledState); + } updateSettings(); } @@ -242,19 +268,16 @@ public class WifiWatchdogStateMachine extends StateMachine { sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); // Disable for wifi only devices. - if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null && - sWifiOnly) { + if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null + && sWifiOnly) { + log("Disabling watchog for wi-fi only device"); putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false); } WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context); wwsm.start(); - wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED); return wwsm; } - /** - * - */ private void setupNetworkReceiver() { mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -263,10 +286,8 @@ public class WifiWatchdogStateMachine extends StateMachine { if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter, - intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget(); - } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { - sendMessage(EVENT_SCAN_RESULTS_AVAILABLE); + obtainMessage(EVENT_RSSI_CHANGE, + intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget(); } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE, intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, @@ -279,7 +300,7 @@ public class WifiWatchdogStateMachine extends StateMachine { mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); } /** @@ -311,39 +332,29 @@ public class WifiWatchdogStateMachine extends StateMachine { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor( - Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS), + Settings.Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_ARP_PINGS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_DNS_PINGS), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS), + Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor( - Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED), false, contentObserver); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL), false, contentObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP) - , false, contentObserver); } /** @@ -375,49 +386,49 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - private boolean rssiStrengthAboveCutoff(int rssi) { - return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF; - } - public void dump(PrintWriter pw) { pw.print("WatchdogStatus: "); - pw.print("State " + getCurrentState()); - pw.println(", network [" + mConnectionInfo + "]"); - pw.print("checkFailures " + mNumCheckFailures); - pw.println(", bssids: " + mBssids); - pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime); + pw.print("State: " + getCurrentState()); + pw.println("mWifiInfo: [" + mWifiInfo + "]"); + pw.println("mLinkProperties: [" + mLinkProperties + "]"); + pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]"); + pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]"); + pw.println("mRssiFetchIntervalMs: [" + mRssiFetchIntervalMs + "]"); + pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]"); + pw.println("mNumArpPings: [" + mNumArpPings + "]"); + pw.println("mMinArpResponses: [" + mMinArpResponses + "]"); + pw.println("mArpPingTimeoutMs: [" + mArpPingTimeoutMs + "]"); + pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]"); + pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]"); + pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]"); } private boolean isWatchdogEnabled() { - return getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); + boolean ret = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); + if (DBG) log("watchdog enabled " + ret); + return ret; } private void updateSettings() { - mDnsCheckShortIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS, - DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS); - mDnsCheckLongIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS, - DEFAULT_DNS_CHECK_LONG_INTERVAL_MS); - mMaxSsidBlacklists = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS, - DEFAULT_MAX_SSID_BLACKLISTS); - mNumDnsPings = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_NUM_DNS_PINGS, - DEFAULT_NUM_DNS_PINGS); - mMinDnsResponses = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES, - DEFAULT_MIN_DNS_RESPONSES); - mDnsPingTimeoutMs = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS, - DEFAULT_DNS_PING_TIMEOUT_MS); - mBlacklistFollowupIntervalMs = Secure.getLong(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS, - DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS); - //TODO: enable this by default after changing watchdog behavior - //Also, update settings description + if (DBG) log("Updating secure settings"); + + mArpCheckIntervalMs = Secure.getLong(mContentResolver, + Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS, + DEFAULT_ARP_CHECK_INTERVAL_MS); + mRssiFetchIntervalMs = Secure.getLong(mContentResolver, + Secure.WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS, + DEFAULT_RSSI_FETCH_INTERVAL_MS); + mNumArpPings = Secure.getInt(mContentResolver, + Secure.WIFI_WATCHDOG_NUM_ARP_PINGS, + DEFAULT_NUM_ARP_PINGS); + mMinArpResponses = Secure.getInt(mContentResolver, + Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES, + DEFAULT_MIN_ARP_RESPONSES); + mArpPingTimeoutMs = Secure.getInt(mContentResolver, + Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS, + DEFAULT_ARP_PING_TIMEOUT_MS); mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, false); + Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true); mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true); mWalledGardenUrl = getSettingsStr(mContentResolver, @@ -426,69 +437,6 @@ public class WifiWatchdogStateMachine extends StateMachine { mWalledGardenIntervalMs = Secure.getLong(mContentResolver, Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS, DEFAULT_WALLED_GARDEN_INTERVAL_MS); - mShowDisabledNotification = getSettingsBoolean(mContentResolver, - Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, true); - } - - /** - * Helper to return wait time left given a min interval and last run - * - * @param interval minimum wait interval - * @param lastTime last time action was performed in - * SystemClock.elapsedRealtime(). Null if never. - * @return non negative time to wait - */ - private static long waitTime(long interval, Long lastTime) { - if (lastTime == null) - return 0; - long wait = interval + lastTime - SystemClock.elapsedRealtime(); - return wait > 0 ? wait : 0; - } - - private static String wifiInfoToStr(WifiInfo wifiInfo) { - if (wifiInfo == null) - return "null"; - return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")"; - } - - /** - * Uses {@link #mConnectionInfo}. - */ - private void updateBssids() { - String curSsid = mConnectionInfo.getSSID(); - List<ScanResult> results = mWifiManager.getScanResults(); - int oldNumBssids = mBssids.size(); - - if (results == null) { - if (DBG) { - log("updateBssids: Got null scan results!"); - } - return; - } - - for (ScanResult result : results) { - if (result == null || result.SSID == null) { - if (DBG) { - log("Received invalid scan result: " + result); - } - continue; - } - if (curSsid.equals(result.SSID)) - mBssids.add(result.BSSID); - } - } - - private void resetWatchdogState() { - if (DBG) { - log("Resetting watchdog state..."); - } - mConnectionInfo = null; - mDisableAPNextFailure = false; - mLastWalledGardenCheckTime = null; - mNumCheckFailures = 0; - mBssids.clear(); - setDisabledNetworkNotificationVisible(false); - setWalledGardenNotificationVisible(false); } private void setWalledGardenNotificationVisible(boolean visible) { @@ -507,7 +455,7 @@ public class WifiWatchdogStateMachine extends StateMachine { CharSequence title = r.getString(R.string.wifi_available_sign_in, 0); CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed, - mConnectionInfo.getSSID()); + mWifiInfo.getSSID()); Notification notification = new Notification(); notification.when = 0; @@ -524,42 +472,12 @@ public class WifiWatchdogStateMachine extends StateMachine { mWalledGardenNotificationShown = visible; } - private void setDisabledNetworkNotificationVisible(boolean visible) { - // If it should be hidden and it is already hidden, then noop - if (!visible && !mDisabledNotificationShown) { - return; - } - - Resources r = Resources.getSystem(); - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (visible) { - CharSequence title = r.getText(R.string.wifi_watchdog_network_disabled); - String msg = mConnectionInfo.getSSID() + - r.getText(R.string.wifi_watchdog_network_disabled_detailed); - - Notification wifiDisabledWarning = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.stat_sys_warning) - .setDefaults(Notification.DEFAULT_ALL) - .setTicker(title) - .setContentTitle(title) - .setContentText(msg) - .setContentIntent(PendingIntent.getActivity(mContext, 0, - new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0)) - .setWhen(System.currentTimeMillis()) - .setAutoCancel(true) - .getNotification(); - - notificationManager.notify(DISABLED_NETWORK_NOTIFICATION_ID, 1, wifiDisabledWarning); - } else { - notificationManager.cancel(DISABLED_NETWORK_NOTIFICATION_ID, 1); + class DefaultState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); } - mDisabledNotificationShown = visible; - } - class DefaultState extends State { @Override public boolean processMessage(Message msg) { switch (msg.what) { @@ -568,11 +486,22 @@ public class WifiWatchdogStateMachine extends StateMachine { if (DBG) { log("Updating wifi-watchdog secure settings"); } - return HANDLED; - } - if (DBG) { - log("Caught message " + msg.what + " in state " + - getCurrentState().getName()); + break; + case EVENT_RSSI_CHANGE: + mCurrentSignalLevel = calculateSignalLevel(msg.arg1); + break; + case EVENT_WIFI_RADIO_STATE_CHANGE: + case EVENT_NETWORK_STATE_CHANGE: + case CMD_ARP_CHECK: + case CMD_DELAYED_WALLED_GARDEN_CHECK: + case CMD_RSSI_FETCH: + case RSSI_FETCH_SUCCEEDED: + case RSSI_FETCH_FAILED: + //ignore + break; + default: + log("Unhandled message " + msg + " in state " + getCurrentState().getName()); + break; } return HANDLED; } @@ -580,12 +509,31 @@ public class WifiWatchdogStateMachine extends StateMachine { class WatchdogDisabledState extends State { @Override + public void enter() { + if (DBG) log(getName() + "\n"); + } + + @Override public boolean processMessage(Message msg) { switch (msg.what) { case EVENT_WATCHDOG_TOGGLED: if (isWatchdogEnabled()) transitionTo(mNotConnectedState); return HANDLED; + case EVENT_NETWORK_STATE_CHANGE: + Intent intent = (Intent) msg.obj; + NetworkInfo networkInfo = (NetworkInfo) + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + + switch (networkInfo.getDetailedState()) { + case VERIFYING_POOR_LINK: + if (DBG) log("Watchdog disabled, verify link"); + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + break; + default: + break; + } + break; } return NOT_HANDLED; } @@ -594,8 +542,6 @@ public class WifiWatchdogStateMachine extends StateMachine { class WatchdogEnabledState extends State { @Override public void enter() { - resetWatchdogState(); - mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); if (DBG) log("WifiWatchdogService enabled"); } @@ -605,95 +551,134 @@ public class WifiWatchdogStateMachine extends StateMachine { case EVENT_WATCHDOG_TOGGLED: if (!isWatchdogEnabled()) transitionTo(mWatchdogDisabledState); - return HANDLED; + break; case EVENT_NETWORK_STATE_CHANGE: - Intent stateChangeIntent = (Intent) msg.obj; + Intent intent = (Intent) msg.obj; NetworkInfo networkInfo = (NetworkInfo) - stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - setDisabledNetworkNotificationVisible(false); - setWalledGardenNotificationVisible(false); - switch (networkInfo.getState()) { - case CONNECTED: - WifiInfo wifiInfo = (WifiInfo) - stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); - if (wifiInfo == null) { - loge("Connected --> WifiInfo object null!"); - return HANDLED; - } - - if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) { - loge("Received wifiInfo object with null elts: " - + wifiInfoToStr(wifiInfo)); - return HANDLED; - } + if (DBG) log("network state change " + networkInfo.getDetailedState()); - initConnection(wifiInfo); - mConnectionInfo = wifiInfo; - mNetEventCounter++; + switch (networkInfo.getDetailedState()) { + case VERIFYING_POOR_LINK: + mLinkProperties = (LinkProperties) intent.getParcelableExtra( + WifiManager.EXTRA_LINK_PROPERTIES); + mWifiInfo = (WifiInfo) intent.getParcelableExtra( + WifiManager.EXTRA_WIFI_INFO); if (mPoorNetworkDetectionEnabled) { - updateBssids(); - transitionTo(mDnsCheckingState); + if (mWifiInfo == null) { + log("Ignoring link verification, mWifiInfo is NULL"); + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + } else { + transitionTo(mVerifyingLinkState); + } + } else { + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + } + break; + case CONNECTED: + if (shouldCheckWalledGarden()) { + transitionTo(mWalledGardenCheckState); } else { - transitionTo(mDelayWalledGardenState); + transitionTo(mOnlineWatchState); } break; default: - mNetEventCounter++; transitionTo(mNotConnectedState); break; } - return HANDLED; + break; case EVENT_WIFI_RADIO_STATE_CHANGE: if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { if (DBG) log("WifiStateDisabling -- Resetting WatchdogState"); - resetWatchdogState(); - mNetEventCounter++; transitionTo(mNotConnectedState); } - return HANDLED; - } - - return NOT_HANDLED; - } - - /** - * @param wifiInfo Info object with non-null ssid and bssid - */ - private void initConnection(WifiInfo wifiInfo) { - if (DBG) { - log("Connected:: old " + wifiInfoToStr(mConnectionInfo) + - " ==> new " + wifiInfoToStr(wifiInfo)); + break; + default: + return NOT_HANDLED; } - if (mConnectionInfo == null || !wifiInfo.getSSID().equals(mConnectionInfo.getSSID())) { - resetWatchdogState(); - } else if (!wifiInfo.getBSSID().equals(mConnectionInfo.getBSSID())) { - mDisableAPNextFailure = false; - } + setWalledGardenNotificationVisible(false); + return HANDLED; } @Override public void exit() { - mContext.unregisterReceiver(mBroadcastReceiver); if (DBG) log("WifiWatchdogService disabled"); } } class NotConnectedState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + } } - class ConnectedState extends State { + class VerifyingLinkState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + //Treat entry as an rssi change + handleRssiChange(); + } + + private void handleRssiChange() { + if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { + //stay here + if (DBG) log("enter VerifyingLinkState, stay level: " + mCurrentSignalLevel); + } else { + if (DBG) log("enter VerifyingLinkState, arp check level: " + mCurrentSignalLevel); + sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0)); + } + } + @Override public boolean processMessage(Message msg) { switch (msg.what) { - case EVENT_SCAN_RESULTS_AVAILABLE: - if (mPoorNetworkDetectionEnabled) { - updateBssids(); + case EVENT_WATCHDOG_SETTINGS_CHANGE: + updateSettings(); + if (!mPoorNetworkDetectionEnabled) { + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); } - return HANDLED; + break; + case EVENT_RSSI_CHANGE: + mCurrentSignalLevel = calculateSignalLevel(msg.arg1); + handleRssiChange(); + break; + case CMD_ARP_CHECK: + if (msg.arg1 == mArpToken) { + if (doArpTest(FULL_ARP_CHECK) == true) { + if (DBG) log("Notify link is good " + mCurrentSignalLevel); + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + } else { + if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel); + sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0), + mArpCheckIntervalMs); + } + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class ConnectedState extends State { + @Override + public void enter() { + if (DBG) log(getName() + "\n"); + } + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); + //STOPSHIP: Remove this at ship + DBG = true; + if (DBG) log("Updated secure settings and turned debug on"); + if (mPoorNetworkDetectionEnabled) { transitionTo(mOnlineWatchState); } else { @@ -705,401 +690,229 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - class DnsCheckingState extends State { - List<InetAddress> mDnsList; - int[] dnsCheckSuccesses; - String dnsCheckLogStr; - String[] dnsResponseStrs; - /** Keeps track of active dns pings. Map is from pingID to index in mDnsList */ - HashMap<Integer, Integer> idDnsMap = new HashMap<Integer, Integer>(); - + class WalledGardenCheckState extends State { + private int mWalledGardenToken = 0; @Override public void enter() { - mDnsList = mDnsPinger.getDnsList(); - int numDnses = mDnsList.size(); - dnsCheckSuccesses = new int[numDnses]; - dnsResponseStrs = new String[numDnses]; - for (int i = 0; i < numDnses; i++) - dnsResponseStrs[i] = ""; - - if (DBG) { - dnsCheckLogStr = String.format("Pinging %s on ssid [%s]: ", - mDnsList, mConnectionInfo.getSSID()); - log(dnsCheckLogStr); - } - - idDnsMap.clear(); - for (int i=0; i < mNumDnsPings; i++) { - for (int j = 0; j < numDnses; j++) { - idDnsMap.put(mDnsPinger.pingDnsAsync(mDnsList.get(j), mDnsPingTimeoutMs, - DNS_START_DELAY_MS + DNS_INTRATEST_PING_INTERVAL_MS * i), j); - } - } + if (DBG) log(getName() + "\n"); + sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK, + ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS); } @Override public boolean processMessage(Message msg) { - if (msg.what != DnsPinger.DNS_PING_RESULT) { - return NOT_HANDLED; - } - - int pingID = msg.arg1; - int pingResponseTime = msg.arg2; - - Integer dnsServerId = idDnsMap.get(pingID); - if (dnsServerId == null) { - loge("Received a Dns response with unknown ID!"); - return HANDLED; - } - - idDnsMap.remove(pingID); - if (pingResponseTime >= 0) - dnsCheckSuccesses[dnsServerId]++; - - if (DBG) { - if (pingResponseTime >= 0) { - dnsResponseStrs[dnsServerId] += "|" + pingResponseTime; - } else { - dnsResponseStrs[dnsServerId] += "|x"; - } - } - - /** - * After a full ping count, if we have more responses than this - * cutoff, the outcome is success; else it is 'failure'. - */ - - /** - * Our final success count will be at least this big, so we're - * guaranteed to succeed. - */ - if (dnsCheckSuccesses[dnsServerId] >= mMinDnsResponses) { - // DNS CHECKS OK, NOW WALLED GARDEN - if (DBG) { - log(makeLogString() + " SUCCESS"); - } - - if (!shouldCheckWalledGarden()) { - transitionTo(mOnlineWatchState); - return HANDLED; - } - - transitionTo(mDelayWalledGardenState); - return HANDLED; - } - - if (idDnsMap.isEmpty()) { - if (DBG) { - log(makeLogString() + " FAILURE"); - } - transitionTo(mDnsCheckFailureState); - return HANDLED; + switch (msg.what) { + case CMD_DELAYED_WALLED_GARDEN_CHECK: + if (msg.arg1 == mWalledGardenToken) { + mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); + if (isWalledGardenConnection()) { + if (DBG) log("Walled garden detected"); + setWalledGardenNotificationVisible(true); + } + transitionTo(mOnlineWatchState); + } + break; + default: + return NOT_HANDLED; } - return HANDLED; } + } - private String makeLogString() { - String logStr = dnsCheckLogStr; - for (String respStr : dnsResponseStrs) - logStr += " [" + respStr + "]"; - return logStr; - } - - @Override - public void exit() { - mDnsPinger.cancelPings(); - } - - private boolean shouldCheckWalledGarden() { - if (!mWalledGardenTestEnabled) { - if (DBG) - log("Skipping walled garden check - disabled"); - return false; - } - long waitTime = waitTime(mWalledGardenIntervalMs, - mLastWalledGardenCheckTime); - if (waitTime > 0) { - if (DBG) { - log("Skipping walled garden check - wait " + - waitTime + " ms."); - } - return false; + class OnlineWatchState extends State { + public void enter() { + if (DBG) log(getName() + "\n"); + if (mPoorNetworkDetectionEnabled) { + //Treat entry as an rssi change + handleRssiChange(); + } else { + transitionTo(mOnlineState); } - return true; } - } - class DelayWalledGardenState extends State { - @Override - public void enter() { - sendMessageDelayed(MESSAGE_DELAYED_WALLED_GARDEN_CHECK, WALLED_GARDEN_START_DELAY_MS); + private void handleRssiChange() { + if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) { + sendPoorLinkDetected(); + } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { + transitionTo(mRssiMonitoringState); + } else { + //stay here + } } @Override public boolean processMessage(Message msg) { switch (msg.what) { - case MESSAGE_DELAYED_WALLED_GARDEN_CHECK: - mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); - if (isWalledGardenConnection()) { - if (DBG) log("Walled garden test complete - walled garden detected"); - transitionTo(mWalledGardenState); + case EVENT_RSSI_CHANGE: + mCurrentSignalLevel = calculateSignalLevel(msg.arg1); + //Ready to avoid bssid again ? + long time = android.os.SystemClock.elapsedRealtime(); + if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ + mMinIntervalArrayIndex]) { + handleRssiChange(); } else { - if (DBG) log("Walled garden test complete - online"); - if (mPoorNetworkDetectionEnabled) { - transitionTo(mOnlineWatchState); - } else { - transitionTo(mOnlineState); - } + if (DBG) log("Early to avoid " + mWifiInfo + " time: " + time + + " last avoided: " + mLastBssidAvoidedTime + + " mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); } - return HANDLED; + break; default: return NOT_HANDLED; } + return HANDLED; } } - class OnlineWatchState extends State { - /** - * Signals a short-wait message is enqueued for the current 'guard' counter - */ - boolean unstableSignalChecks = false; - - /** - * The signal is unstable. We should enqueue a short-wait check, if one is enqueued - * already - */ - boolean signalUnstable = false; - - /** - * A monotonic counter to ensure that at most one check message will be processed from any - * set of check messages currently enqueued. Avoids duplicate checks when a low-signal - * event is observed. - */ - int checkGuard = 0; - Long lastCheckTime = null; - - /** Keeps track of dns pings. Map is from pingID to InetAddress used for ping */ - HashMap<Integer, InetAddress> pingInfoMap = new HashMap<Integer, InetAddress>(); - - @Override + class RssiMonitoringState extends State { public void enter() { - lastCheckTime = SystemClock.elapsedRealtime(); - signalUnstable = false; - checkGuard++; - unstableSignalChecks = false; - pingInfoMap.clear(); - triggerSingleDnsCheck(); + if (DBG) log(getName() + "\n"); + sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); } - @Override public boolean processMessage(Message msg) { switch (msg.what) { case EVENT_RSSI_CHANGE: - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("Rssi change message out of sync, ignoring"); - } - return HANDLED; - } - int newRssi = msg.arg2; - signalUnstable = !rssiStrengthAboveCutoff(newRssi); - if (DBG) { - log("OnlineWatchState:: new rssi " + newRssi + " --> level " + - WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS)); - } - - if (signalUnstable && !unstableSignalChecks) { - if (DBG) { - log("Sending triggered check msg"); - } - triggerSingleDnsCheck(); - } - return HANDLED; - case MESSAGE_SINGLE_DNS_CHECK: - if (msg.arg1 != checkGuard) { - if (DBG) { - log("Single check msg out of sync, ignoring."); - } - return HANDLED; - } - lastCheckTime = SystemClock.elapsedRealtime(); - pingInfoMap.clear(); - for (InetAddress curDns: mDnsPinger.getDnsList()) { - pingInfoMap.put(mDnsPinger.pingDnsAsync(curDns, mDnsPingTimeoutMs, 0), - curDns); + mCurrentSignalLevel = calculateSignalLevel(msg.arg1); + if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) { + sendPoorLinkDetected(); + } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { + //stay here; + } else { + //We dont need frequent RSSI monitoring any more + transitionTo(mOnlineWatchState); } - return HANDLED; - case DnsPinger.DNS_PING_RESULT: - InetAddress curDnsServer = pingInfoMap.get(msg.arg1); - if (curDnsServer == null) { - return HANDLED; + break; + case CMD_RSSI_FETCH: + if (msg.arg1 == mRssiFetchToken) { + mWsmChannel.sendMessage(RSSI_FETCH); + sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), + mRssiFetchIntervalMs); } - pingInfoMap.remove(msg.arg1); - int responseTime = msg.arg2; - if (responseTime >= 0) { - if (DBG) { - log("Single DNS ping OK. Response time: " - + responseTime + " from DNS " + curDnsServer); - } - pingInfoMap.clear(); - - checkGuard++; - unstableSignalChecks = false; - triggerSingleDnsCheck(); + break; + case RSSI_FETCH_SUCCEEDED: + int rssi = msg.arg1; + if (DBG) log("RSSI_FETCH_SUCCEEDED: " + rssi); + if (msg.arg1 < RSSI_MONITOR_THRESHOLD) { + mRssiMonitorCount++; } else { - if (pingInfoMap.isEmpty()) { - if (DBG) { - log("Single dns ping failure. All dns servers failed, " - + "starting full checks."); - } - transitionTo(mDnsCheckingState); - } + mRssiMonitorCount = 0; } - return HANDLED; - } - return NOT_HANDLED; - } - @Override - public void exit() { - mDnsPinger.cancelPings(); - } - - /** - * Times a dns check with an interval based on {@link #signalUnstable} - */ - private void triggerSingleDnsCheck() { - long waitInterval; - if (signalUnstable) { - waitInterval = mDnsCheckShortIntervalMs; - unstableSignalChecks = true; - } else { - waitInterval = mDnsCheckLongIntervalMs; + if (mRssiMonitorCount > RSSI_MONITOR_COUNT) { + sendPoorLinkDetected(); + ++mRssiFetchToken; + } + break; + case RSSI_FETCH_FAILED: + //can happen if we are waiting to get a disconnect notification + if (DBG) log("RSSI_FETCH_FAILED"); + break; + default: + return NOT_HANDLED; } - sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0), - waitTime(waitInterval, lastCheckTime)); + return HANDLED; } - } - + } /* Child state of ConnectedState indicating that we are online * and there is nothing to do */ class OnlineState extends State { - } - - class DnsCheckFailureState extends State { - @Override public void enter() { - mNumCheckFailures++; - obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget(); + if (DBG) log(getName() + "\n"); } + } - @Override - public boolean processMessage(Message msg) { - if (msg.what != MESSAGE_HANDLE_BAD_AP) { - return NOT_HANDLED; - } + private boolean shouldCheckWalledGarden() { + if (!mWalledGardenTestEnabled) { + if (DBG) log("Skipping walled garden check - disabled"); + return false; + } - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("Msg out of sync, ignoring..."); - } - return HANDLED; + long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime) + - SystemClock.elapsedRealtime(); + + if (mLastWalledGardenCheckTime != 0 && waitTime > 0) { + if (DBG) { + log("Skipping walled garden check - wait " + + waitTime + " ms."); } + return false; + } + return true; + } - if (mDisableAPNextFailure || mNumCheckFailures >= mBssids.size() - || mNumCheckFailures >= mMaxSsidBlacklists) { - if (sWifiOnly) { - log("Would disable bad network, but device has no mobile data!" + - " Going idle..."); - // This state should be called idle -- will be changing flow. - transitionTo(mNotConnectedState); - return HANDLED; - } + private boolean doArpTest(int type) { + boolean success; - // TODO : Unban networks if they had low signal ? - log("Disabling current SSID " + wifiInfoToStr(mConnectionInfo) - + ". " + "numCheckFailures " + mNumCheckFailures - + ", numAPs " + mBssids.size()); - int networkId = mConnectionInfo.getNetworkId(); - if (!mHasConnectedWifiManager) { - mWifiManager.asyncConnect(mContext, getHandler()); - mHasConnectedWifiManager = true; - } - mWifiManager.disableNetwork(networkId, WifiConfiguration.DISABLED_DNS_FAILURE); - if (mShowDisabledNotification && mConnectionInfo.isExplicitConnect()) { - setDisabledNetworkNotificationVisible(true); - } - transitionTo(mNotConnectedState); - } else { - log("Blacklisting current BSSID. " + wifiInfoToStr(mConnectionInfo) - + "numCheckFailures " + mNumCheckFailures + ", numAPs " + mBssids.size()); + String iface = mLinkProperties.getInterfaceName(); + String mac = mWifiInfo.getMacAddress(); + InetAddress inetAddress = null; + InetAddress gateway = null; - mWifiManager.addToBlacklist(mConnectionInfo.getBSSID()); - mWifiManager.reassociate(); - transitionTo(mBlacklistedApState); - } - return HANDLED; + for (LinkAddress la : mLinkProperties.getLinkAddresses()) { + inetAddress = la.getAddress(); + break; } - } - class WalledGardenState extends State { - @Override - public void enter() { - obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget(); + for (RouteInfo route : mLinkProperties.getRoutes()) { + gateway = route.getGateway(); + break; } - @Override - public boolean processMessage(Message msg) { - if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) { - return NOT_HANDLED; - } + if (DBG) log("ARP " + iface + "addr: " + inetAddress + "mac: " + mac + "gw: " + gateway); - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("WalledGardenState::Msg out of sync, ignoring..."); - } - return HANDLED; - } - setWalledGardenNotificationVisible(true); - if (mPoorNetworkDetectionEnabled) { - transitionTo(mOnlineWatchState); + try { + ArpPeer peer = new ArpPeer(iface, inetAddress, mac, gateway); + if (type == SINGLE_ARP_CHECK) { + success = (peer.doArp(mArpPingTimeoutMs) != null); + if (DBG) log("single ARP test result: " + success); } else { - transitionTo(mOnlineState); + int responses = 0; + for (int i=0; i < mNumArpPings; i++) { + if(peer.doArp(mArpPingTimeoutMs) != null) responses++; + } + if (DBG) log("full ARP test result: " + responses + "/" + mNumArpPings); + success = (responses >= mMinArpResponses); } - return HANDLED; + peer.close(); + } catch (SocketException se) { + //Consider an Arp socket creation issue as a successful Arp + //test to avoid any wifi connectivity issues + loge("ARP test initiation failure: " + se); + success = true; } + + return success; } - class BlacklistedApState extends State { - @Override - public void enter() { - mDisableAPNextFailure = true; - sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0), - mBlacklistFollowupIntervalMs); - } + private int calculateSignalLevel(int rssi) { + int signalLevel = WifiManager.calculateSignalLevel(rssi, + WifiManager.RSSI_LEVELS); + if (DBG) log("RSSI current: " + mCurrentSignalLevel + "new: " + rssi + ", " + signalLevel); + return signalLevel; + } - @Override - public boolean processMessage(Message msg) { - if (msg.what != MESSAGE_NETWORK_FOLLOWUP) { - return NOT_HANDLED; - } + private void sendPoorLinkDetected() { + if (DBG) log("send POOR_LINK_DETECTED " + mWifiInfo); + mWsmChannel.sendMessage(POOR_LINK_DETECTED); - if (msg.arg1 != mNetEventCounter) { - if (DBG) { - log("BlacklistedApState::Msg out of sync, ignoring..."); - } - return HANDLED; - } + long time = android.os.SystemClock.elapsedRealtime(); + if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ + MIN_INTERVAL_AVOID_BSSID_MS.length - 1]) { + mMinIntervalArrayIndex = 1; + if (DBG) log("set mMinIntervalArrayIndex to 1"); + } else { - transitionTo(mDnsCheckingState); - return HANDLED; + if (mMinIntervalArrayIndex < MIN_INTERVAL_AVOID_BSSID_MS.length - 1) { + mMinIntervalArrayIndex++; + } + if (DBG) log("mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); } - } + mLastBssidAvoidedTime = android.os.SystemClock.elapsedRealtime(); + } /** * Convenience function for retrieving a single secure settings value @@ -1151,11 +964,11 @@ public class WifiWatchdogStateMachine extends StateMachine { return Settings.Secure.putInt(cr, name, value ? 1 : 0); } - private void log(String s) { + private static void log(String s) { Log.d(TAG, s); } - private void loge(String s) { + private static void loge(String s) { Log.e(TAG, s); } } diff --git a/wifi/java/android/net/wifi/WpsInfo.java b/wifi/java/android/net/wifi/WpsInfo.java index f70e5af..b80df21 100644 --- a/wifi/java/android/net/wifi/WpsInfo.java +++ b/wifi/java/android/net/wifi/WpsInfo.java @@ -16,9 +16,6 @@ package android.net.wifi; -import android.net.LinkProperties; -import android.net.wifi.WifiConfiguration.IpAssignment; -import android.net.wifi.WifiConfiguration.ProxySettings; import android.os.Parcelable; import android.os.Parcel; @@ -51,22 +48,10 @@ public class WpsInfo implements Parcelable { /** Passed with pin method configuration */ public String pin; - /** @hide */ - public IpAssignment ipAssignment; - - /** @hide */ - public ProxySettings proxySettings; - - /** @hide */ - public LinkProperties linkProperties; - public WpsInfo() { setup = INVALID; BSSID = null; pin = null; - ipAssignment = IpAssignment.UNASSIGNED; - proxySettings = ProxySettings.UNASSIGNED; - linkProperties = new LinkProperties(); } public String toString() { @@ -77,12 +62,6 @@ public class WpsInfo implements Parcelable { sbuf.append('\n'); sbuf.append(" pin: ").append(pin); sbuf.append('\n'); - sbuf.append("IP assignment: " + ipAssignment.toString()); - sbuf.append("\n"); - sbuf.append("Proxy settings: " + proxySettings.toString()); - sbuf.append("\n"); - sbuf.append(linkProperties.toString()); - sbuf.append("\n"); return sbuf.toString(); } @@ -97,9 +76,6 @@ public class WpsInfo implements Parcelable { setup = source.setup; BSSID = source.BSSID; pin = source.pin; - ipAssignment = source.ipAssignment; - proxySettings = source.proxySettings; - linkProperties = new LinkProperties(source.linkProperties); } } @@ -108,9 +84,6 @@ public class WpsInfo implements Parcelable { dest.writeInt(setup); dest.writeString(BSSID); dest.writeString(pin); - dest.writeString(ipAssignment.name()); - dest.writeString(proxySettings.name()); - dest.writeParcelable(linkProperties, flags); } /** Implement the Parcelable interface */ @@ -121,9 +94,6 @@ public class WpsInfo implements Parcelable { config.setup = in.readInt(); config.BSSID = in.readString(); config.pin = in.readString(); - config.ipAssignment = IpAssignment.valueOf(in.readString()); - config.proxySettings = ProxySettings.valueOf(in.readString()); - config.linkProperties = in.readParcelable(null); return config; } diff --git a/wifi/java/android/net/wifi/WpsStateMachine.java b/wifi/java/android/net/wifi/WpsStateMachine.java deleted file mode 100644 index c14a8db..0000000 --- a/wifi/java/android/net/wifi/WpsStateMachine.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.wifi; - -import com.android.internal.util.AsyncChannel; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; - -import android.content.Context; -import android.content.Intent; -import android.net.wifi.StateChangeResult; -import android.net.wifi.WpsResult.Status; -import android.os.Handler; -import android.os.Message; -import android.os.Parcelable; -import android.util.Log; - -/** - * Manages a WPS connection. - * - * WPS consists as a series of EAP exchange triggered - * by a user action that leads to a successful connection - * after automatic creation of configuration in the - * supplicant. We currently support the following methods - * of WPS setup - * 1. Pin method: Pin can be either obtained from the device - * or from the access point to connect to. - * 2. Push button method: This involves pushing a button on - * the access point and the device - * - * After a successful WPS setup, the state machine - * reloads the configuration and updates the IP and proxy - * settings, if any. - */ -class WpsStateMachine extends StateMachine { - - private static final String TAG = "WpsStateMachine"; - private static final boolean DBG = false; - - private WifiStateMachine mWifiStateMachine; - - private WpsInfo mWpsInfo; - - private Context mContext; - AsyncChannel mReplyChannel = new AsyncChannel(); - - private State mDefaultState = new DefaultState(); - private State mInactiveState = new InactiveState(); - private State mActiveState = new ActiveState(); - - public WpsStateMachine(Context context, WifiStateMachine wsm, Handler target) { - super(TAG, target.getLooper()); - - mContext = context; - mWifiStateMachine = wsm; - addState(mDefaultState); - addState(mInactiveState, mDefaultState); - addState(mActiveState, mDefaultState); - - setInitialState(mInactiveState); - - //start the state machine - start(); - } - - - /******************************************************** - * HSM states - *******************************************************/ - - class DefaultState extends State { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - WpsInfo wpsConfig; - switch (message.what) { - case WifiStateMachine.CMD_START_WPS: - mWpsInfo = (WpsInfo) message.obj; - WpsResult result; - switch (mWpsInfo.setup) { - case WpsInfo.PBC: - result = WifiConfigStore.startWpsPbc(mWpsInfo); - break; - case WpsInfo.KEYPAD: - result = WifiConfigStore.startWpsWithPinFromAccessPoint(mWpsInfo); - break; - case WpsInfo.DISPLAY: - result = WifiConfigStore.startWpsWithPinFromDevice(mWpsInfo); - break; - default: - result = new WpsResult(Status.FAILURE); - Log.e(TAG, "Invalid setup for WPS"); - break; - } - mReplyChannel.replyToMessage(message, WifiManager.CMD_WPS_COMPLETED, result); - if (result.status == Status.SUCCESS) { - transitionTo(mActiveState); - } else { - Log.e(TAG, "Failed to start WPS with config " + mWpsInfo.toString()); - } - break; - case WifiStateMachine.CMD_RESET_WPS_STATE: - transitionTo(mInactiveState); - break; - default: - Log.e(TAG, "Failed to handle " + message); - break; - } - return HANDLED; - } - } - - class ActiveState extends State { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - } - - @Override - public boolean processMessage(Message message) { - boolean retValue = HANDLED; - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - SupplicantState supState = (SupplicantState) stateChangeResult.state; - switch (supState) { - case COMPLETED: - /* During WPS setup, all other networks are disabled. After - * a successful connect a new config is created in the supplicant. - * - * We need to enable all networks after a successful connection - * and the configuration list needs to be reloaded from the supplicant. - */ - Log.d(TAG, "WPS set up successful"); - WifiConfigStore.enableAllNetworks(); - WifiConfigStore.loadConfiguredNetworks(); - WifiConfigStore.updateIpAndProxyFromWpsConfig( - stateChangeResult.networkId, mWpsInfo); - mWifiStateMachine.sendMessage(WifiStateMachine.WPS_COMPLETED_EVENT); - transitionTo(mInactiveState); - break; - case INACTIVE: - /* A failed WPS connection */ - Log.d(TAG, "WPS set up failed, enabling other networks"); - WifiConfigStore.enableAllNetworks(); - mWifiStateMachine.sendMessage(WifiStateMachine.WPS_COMPLETED_EVENT); - transitionTo(mInactiveState); - break; - default: - if (DBG) Log.d(TAG, "Ignoring supplicant state " + supState.name()); - break; - } - break; - case WifiStateMachine.CMD_START_WPS: - /* Ignore request and send an in progress message */ - mReplyChannel.replyToMessage(message, WifiManager.CMD_WPS_COMPLETED, - new WpsResult(Status.IN_PROGRESS)); - break; - default: - retValue = NOT_HANDLED; - } - return retValue; - } - } - - class InactiveState extends State { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - } - - @Override - public boolean processMessage(Message message) { - boolean retValue = HANDLED; - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - //Ignore supplicant state changes - case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: - break; - default: - retValue = NOT_HANDLED; - } - return retValue; - } - } - -} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java index e0c1b13..6aea090 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java @@ -84,19 +84,19 @@ public class WifiP2pConfig implements Parcelable { } catch (NumberFormatException e) { devPasswdId = 0; } - //As defined in wps/wps_defs.h + //Based on definitions in wps/wps_defs.h switch (devPasswdId) { - case 0x00: - wps.setup = WpsInfo.LABEL; - break; + //DEV_PW_USER_SPECIFIED = 0x0001, case 0x01: - wps.setup = WpsInfo.KEYPAD; + wps.setup = WpsInfo.DISPLAY; break; + //DEV_PW_PUSHBUTTON = 0x0004, case 0x04: wps.setup = WpsInfo.PBC; break; + //DEV_PW_REGISTRAR_SPECIFIED = 0x0005 case 0x05: - wps.setup = WpsInfo.DISPLAY; + wps.setup = WpsInfo.KEYPAD; break; default: wps.setup = WpsInfo.PBC; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java index 1b0c301..afdc9be 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.util.Log; import java.util.regex.Pattern; +import java.util.regex.Matcher; /** * A class representing a Wi-Fi p2p device @@ -34,23 +35,12 @@ public class WifiP2pDevice implements Parcelable { /** * The device name is a user friendly string to identify a Wi-Fi p2p device */ - public String deviceName; + public String deviceName = ""; /** * The device MAC address uniquely identifies a Wi-Fi p2p device */ - public String deviceAddress; - - /** - * interfaceAddress - * - * This address is used during group owner negotiation as the Intended - * P2P Interface Address and the group interface will be created with - * address as the local address in case of successfully completed - * negotiation. - * @hide - */ - public String interfaceAddress; + public String deviceAddress = ""; /** * Primary device type identifies the type of device. For example, an application @@ -117,6 +107,43 @@ public class WifiP2pDevice implements Parcelable { /** Device connection status */ public int status = UNAVAILABLE; + /** Detailed device string pattern + * Example: + * P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 + * pri_dev_type=1-0050F204-1 name='p2p-TEST1' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + */ + private static final Pattern detailedDevicePattern = Pattern.compile( + "((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " + + "(\\d+ )?" + + "p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " + + "pri_dev_type=(\\d+-[0-9a-fA-F]+-\\d+) " + + "name='(.*)' " + + "config_methods=(0x[0-9a-fA-F]+) " + + "dev_capab=(0x[0-9a-fA-F]+) " + + "group_capab=(0x[0-9a-fA-F]+)" + ); + + /** 2 token device address pattern + * Example: + * P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13 + * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 + */ + private static final Pattern twoTokenPattern = Pattern.compile( + "(p2p_dev_addr=)?((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" + ); + + /** 3 token device address pattern + * Example: + * AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13 + * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13 + */ + private static final Pattern threeTokenPattern = Pattern.compile( + "(?:[0-9a-f]{2}:){5}[0-9a-f]{2} p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" + ); + + public WifiP2pDevice() { } @@ -128,69 +155,55 @@ public class WifiP2pDevice implements Parcelable { * * P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13 * - * fa:7b:7a:42:02:13 - * - * P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 - * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 - * group_capab=0x0 + * AP-STA-CONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54] * - * P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 - * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 - * group_capab=0x0 + * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54] * - * P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607 p2p_dev_addr=42:fc:89:e1:e2:27 - * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 - * group_capab=0x0 + * fa:7b:7a:42:02:13 * * Note: The events formats can be looked up in the wpa_supplicant code * @hide */ public WifiP2pDevice(String string) throws IllegalArgumentException { - String[] tokens = string.split(" "); + String[] tokens = string.split("[ \n]"); + Matcher match; if (tokens.length < 1) { throw new IllegalArgumentException("Malformed supplicant event"); } - /* Just a device address */ - if (tokens.length == 1) { - deviceAddress = string; - return; - } - - for (String token : tokens) { - String[] nameValue = token.split("="); - if (nameValue.length != 2) continue; - - if (nameValue[0].equals("p2p_dev_addr")) { - deviceAddress = nameValue[1]; - continue; - } - - if (nameValue[0].equals("pri_dev_type")) { - primaryDeviceType = nameValue[1]; - continue; - } - - if (nameValue[0].equals("name")) { - deviceName = trimQuotes(nameValue[1]); - continue; - } - - if (nameValue[0].equals("config_methods")) { - wpsConfigMethodsSupported = parseHex(nameValue[1]); - continue; - } - - if (nameValue[0].equals("dev_capab")) { - deviceCapability = parseHex(nameValue[1]); - continue; - } - - if (nameValue[0].equals("group_capab")) { - groupCapability = parseHex(nameValue[1]); - continue; - } + switch (tokens.length) { + case 1: + /* Just a device address */ + deviceAddress = string; + return; + case 2: + match = twoTokenPattern.matcher(string); + if (!match.find()) { + throw new IllegalArgumentException("Malformed supplicant event"); + } + deviceAddress = match.group(2); + return; + case 3: + match = threeTokenPattern.matcher(string); + if (!match.find()) { + throw new IllegalArgumentException("Malformed supplicant event"); + } + deviceAddress = match.group(1); + return; + default: + match = detailedDevicePattern.matcher(string); + if (!match.find()) { + throw new IllegalArgumentException("Malformed supplicant event"); + } + + deviceAddress = match.group(3); + primaryDeviceType = match.group(4); + deviceName = match.group(5); + wpsConfigMethodsSupported = parseHex(match.group(6)); + deviceCapability = parseHex(match.group(7)); + groupCapability = parseHex(match.group(8)); + break; } if (tokens[0].startsWith("P2P-DEVICE-FOUND")) { @@ -239,7 +252,6 @@ public class WifiP2pDevice implements Parcelable { StringBuffer sbuf = new StringBuffer(); sbuf.append("Device: ").append(deviceName); sbuf.append("\n deviceAddress: ").append(deviceAddress); - sbuf.append("\n interfaceAddress: ").append(interfaceAddress); sbuf.append("\n primary type: ").append(primaryDeviceType); sbuf.append("\n secondary type: ").append(secondaryDeviceType); sbuf.append("\n wps: ").append(wpsConfigMethodsSupported); @@ -259,7 +271,6 @@ public class WifiP2pDevice implements Parcelable { if (source != null) { deviceName = source.deviceName; deviceAddress = source.deviceAddress; - interfaceAddress = source.interfaceAddress; primaryDeviceType = source.primaryDeviceType; secondaryDeviceType = source.secondaryDeviceType; wpsConfigMethodsSupported = source.wpsConfigMethodsSupported; @@ -273,7 +284,6 @@ public class WifiP2pDevice implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(deviceName); dest.writeString(deviceAddress); - dest.writeString(interfaceAddress); dest.writeString(primaryDeviceType); dest.writeString(secondaryDeviceType); dest.writeInt(wpsConfigMethodsSupported); @@ -289,7 +299,6 @@ public class WifiP2pDevice implements Parcelable { WifiP2pDevice device = new WifiP2pDevice(); device.deviceName = in.readString(); device.deviceAddress = in.readString(); - device.interfaceAddress = in.readString(); device.primaryDeviceType = in.readString(); device.secondaryDeviceType = in.readString(); device.wpsConfigMethodsSupported = in.readInt(); @@ -304,14 +313,6 @@ public class WifiP2pDevice implements Parcelable { } }; - private String trimQuotes(String str) { - str = str.trim(); - if (str.startsWith("'") && str.endsWith("'")) { - return str.substring(1, str.length()-1); - } - return str; - } - //supported formats: 0x1abc, 0X1abc, 1abc private int parseHex(String hexString) { int num = 0; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java index 9ce2545..3751727 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java @@ -24,6 +24,7 @@ import android.util.Log; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; /** * A class representing a Wi-Fi P2p device list @@ -32,24 +33,28 @@ import java.util.Collections; */ public class WifiP2pDeviceList implements Parcelable { - private Collection<WifiP2pDevice> mDevices; + private HashMap<String, WifiP2pDevice> mDevices; public WifiP2pDeviceList() { - mDevices = new ArrayList<WifiP2pDevice>(); + mDevices = new HashMap<String, WifiP2pDevice>(); } /** copy constructor */ public WifiP2pDeviceList(WifiP2pDeviceList source) { if (source != null) { - mDevices = source.getDeviceList(); + for (WifiP2pDevice d : source.getDeviceList()) { + mDevices.put(d.deviceAddress, d); + } } } /** @hide */ public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) { - mDevices = new ArrayList<WifiP2pDevice>(); + mDevices = new HashMap<String, WifiP2pDevice>(); for (WifiP2pDevice device : devices) { - mDevices.add(device); + if (device.deviceAddress != null) { + mDevices.put(device.deviceAddress, device); + } } } @@ -62,37 +67,42 @@ public class WifiP2pDeviceList implements Parcelable { /** @hide */ public void update(WifiP2pDevice device) { - if (device == null) return; - for (WifiP2pDevice d : mDevices) { - //Found, update fields that can change - if (d.equals(device)) { - d.deviceName = device.deviceName; - d.primaryDeviceType = device.primaryDeviceType; - d.secondaryDeviceType = device.secondaryDeviceType; - d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported; - d.deviceCapability = device.deviceCapability; - d.groupCapability = device.groupCapability; - return; - } + if (device == null || device.deviceAddress == null) return; + WifiP2pDevice d = mDevices.get(device.deviceAddress); + if (d != null) { + d.deviceName = device.deviceName; + d.primaryDeviceType = device.primaryDeviceType; + d.secondaryDeviceType = device.secondaryDeviceType; + d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported; + d.deviceCapability = device.deviceCapability; + d.groupCapability = device.groupCapability; + return; } //Not found, add a new one - mDevices.add(device); + mDevices.put(device.deviceAddress, device); + } + + /** @hide */ + public WifiP2pDevice get(String deviceAddress) { + if (deviceAddress == null) return null; + + return mDevices.get(deviceAddress); } /** @hide */ public boolean remove(WifiP2pDevice device) { - if (device == null) return false; - return mDevices.remove(device); + if (device == null || device.deviceAddress == null) return false; + return mDevices.remove(device.deviceAddress) != null; } /** Get the list of devices */ public Collection<WifiP2pDevice> getDeviceList() { - return Collections.unmodifiableCollection(mDevices); + return Collections.unmodifiableCollection(mDevices.values()); } public String toString() { StringBuffer sbuf = new StringBuffer(); - for (WifiP2pDevice device : mDevices) { + for (WifiP2pDevice device : mDevices.values()) { sbuf.append("\n").append(device); } return sbuf.toString(); @@ -106,7 +116,7 @@ public class WifiP2pDeviceList implements Parcelable { /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mDevices.size()); - for(WifiP2pDevice device : mDevices) { + for(WifiP2pDevice device : mDevices.values()) { dest.writeParcelable(device, flags); } } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java index 9473993..c30cc73 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java @@ -23,6 +23,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Collection; import java.util.Collections; +import java.util.regex.Pattern; +import java.util.regex.Matcher; /** * A class representing a Wi-Fi P2p group @@ -48,6 +50,15 @@ public class WifiP2pGroup implements Parcelable { private String mInterface; + /** P2P group started string pattern */ + private static final Pattern groupStartedPattern = Pattern.compile( + "ssid=\"(.+)\" " + + "freq=(\\d+) " + + "(?:psk=)?([0-9a-fA-F]{64})?" + + "(?:passphrase=)?(?:\"(.{8,63})\")? " + + "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" + ); + public WifiP2pGroup() { } @@ -78,24 +89,18 @@ public class WifiP2pGroup implements Parcelable { mInterface = tokens[1]; mIsGroupOwner = tokens[2].equals("GO"); - for (String token : tokens) { - String[] nameValue = token.split("="); - if (nameValue.length != 2) continue; - - if (nameValue[0].equals("ssid")) { - mNetworkName = nameValue[1]; - continue; - } + Matcher match = groupStartedPattern.matcher(supplicantEvent); + if (!match.find()) { + return; + } - if (nameValue[0].equals("passphrase")) { - mPassphrase = nameValue[1]; - continue; - } + mNetworkName = match.group(1); + //freq and psk are unused right now + //int freq = Integer.parseInt(match.group(2)); + //String psk = match.group(3); + mPassphrase = match.group(4); + mOwner = new WifiP2pDevice(match.group(5)); - if (nameValue[0].equals("go_dev_addr")) { - mOwner = new WifiP2pDevice(nameValue[1]); - } - } } else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) { for (String token : tokens) { String[] nameValue = token.split("="); @@ -172,6 +177,12 @@ public class WifiP2pGroup implements Parcelable { return mClients.size() == 0; } + /** @hide Returns {@code true} if the device is part of the group */ + public boolean contains(WifiP2pDevice device) { + if (mOwner.equals(device) || mClients.contains(device)) return true; + return false; + } + /** Get the list of clients currently part of the p2p group */ public Collection<WifiP2pDevice> getClientList() { return Collections.unmodifiableCollection(mClients); diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 9205300..df14bb9 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -21,6 +21,14 @@ import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; import android.net.ConnectivityManager; import android.net.IConnectivityManager; +import android.net.nsd.DnsSdTxtRecord; +import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo; +import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceResponse; +import android.net.wifi.p2p.nsd.WifiP2pServiceInfo; +import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; +import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; +import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo; +import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceResponse; import android.os.Binder; import android.os.IBinder; import android.os.Handler; @@ -36,6 +44,7 @@ import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import java.util.HashMap; +import java.util.List; /** * This class provides the API for managing Wi-Fi peer-to-peer connectivity. This lets an @@ -47,21 +56,20 @@ import java.util.HashMap; * callbacks provided by the application. The application needs to do an initialization with * {@link #initialize} before doing any p2p operation. * - * <p> Application actions {@link #discoverPeers}, {@link #connect}, {@link #cancelConnect}, - * {@link #createGroup} and {@link #removeGroup} need a {@link ActionListener} instance for - * receiving callbacks {@link ActionListener#onSuccess} or {@link ActionListener#onFailure}. - * Action callbacks indicate whether the initiation of the action was a success or a failure. + * <p> Most application calls need a {@link ActionListener} instance for receiving callbacks + * {@link ActionListener#onSuccess} or {@link ActionListener#onFailure}. Action callbacks + * indicate whether the initiation of the action was a success or a failure. * Upon failure, the reason of failure can be one of {@link #ERROR}, {@link #P2P_UNSUPPORTED} * or {@link #BUSY}. * * <p> An application can initiate discovery of peers with {@link #discoverPeers}. An initiated * discovery request from an application stays active until the device starts connecting to a peer - * or forms a p2p group. The {@link ActionListener} callbacks provide feedback on whether the - * discovery initiation was successful or failure. Additionally, applications can listen - * to {@link #WIFI_P2P_PEERS_CHANGED_ACTION} intent action to know when the peer list changes. + * ,forms a p2p group or there is an explicit {@link #stopPeerDiscovery}. + * Applications can listen to {@link #WIFI_P2P_DISCOVERY_CHANGED_ACTION} to know if a peer-to-peer + * discovery is running or stopped. Additionally, {@link #WIFI_P2P_PEERS_CHANGED_ACTION} indicates + * if the peer list has changed. * - * <p> When the peer list change intent {@link #WIFI_P2P_PEERS_CHANGED_ACTION} is received - * or when an application needs to fetch the current list of peers, it can request the list + * <p> When an application needs to fetch the current list of peers, it can request the list * of peers with {@link #requestPeers}. When the peer list is available * {@link PeerListListener#onPeersAvailable} is called with the device list. * @@ -69,7 +77,7 @@ import java.util.HashMap; * {@link WifiP2pConfig} for details on setting up the configuration. For communication with legacy * Wi-Fi devices that do not support p2p, an app can create a group using {@link #createGroup} * which creates an access point whose details can be fetched with {@link #requestGroupInfo}. -* + * * <p> After a successful group formation through {@link #createGroup} or through {@link #connect}, * use {@link #requestConnectionInfo} to fetch the connection details. The connection info * {@link WifiP2pInfo} contains the address of the group owner @@ -77,8 +85,36 @@ import java.util.HashMap; * if the current device is a p2p group owner. A p2p client can thus communicate with * the p2p group owner through a socket connection. * - * <p> Android has no platform support for service discovery yet, so applications could - * run a service discovery protocol to discover services on the peer-to-peer netework. + * <p> With peer discovery using {@link #discoverPeers}, an application discovers the neighboring + * peers, but has no good way to figure out which peer to establish a connection with. For example, + * if a game application is interested in finding all the neighboring peers that are also running + * the same game, it has no way to find out until after the connection is setup. Pre-association + * service discovery is meant to address this issue of filtering the peers based on the running + * services. + * + * <p>With pre-association service discovery, an application can advertise a service for a + * application on a peer device prior to a connection setup between the devices. + * Currently, DNS based service discovery (Bonjour) and Upnp are the higher layer protocols + * supported. Get Bonjour resources at dns-sd.org and Upnp resources at upnp.org + * As an example, a video application can discover a Upnp capable media renderer + * prior to setting up a Wi-fi p2p connection with the device. + * + * <p> An application can advertise a Upnp or a Bonjour service with a call to + * {@link #addLocalService}. After a local service is added, + * the framework automatically responds to a peer application discovering the service prior + * to establishing a p2p connection. A call to {@link #removeLocalService} removes a local + * service and {@link #clearLocalServices} can be used to clear all local services. + * + * <p> An application that is looking for peer devices that support certain services + * can do so with a call to {@link #discoverServices}. Prior to initiating the discovery, + * application can add service discovery request with a call to {@link #addServiceRequest}, + * remove a service discovery request with a call to {@link #removeServiceRequest} or clear + * all requests with a call to {@link #clearServiceRequests}. When no service requests remain, + * a previously running service discovery will stop. + * + * The application is notified of a result of service discovery request through listener callbacks + * set through {@link #setDnsSdResponseListeners} for Bonjour or + * {@link #setUpnpServiceResponseListener} for Upnp. * * <p class="note"><strong>Note:</strong> * Registering an application handler with {@link #initialize} requires the permissions @@ -182,6 +218,41 @@ public class WifiP2pManager { "android.net.wifi.p2p.PEERS_CHANGED"; /** + * Broadcast intent action indicating that peer discovery has either started or stopped. + * One extra {@link #EXTRA_DISCOVERY_STATE} indicates whether discovery has started + * or stopped. + * + * Note that discovery will be stopped during a connection setup. If the application tries + * to re-initiate discovery during this time, it can fail. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String WIFI_P2P_DISCOVERY_CHANGED_ACTION = + "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE"; + + /** + * The lookup key for an int that indicates whether p2p discovery has started or stopped. + * Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}. + * + * @see #WIFI_P2P_DISCOVERY_STARTED + * @see #WIFI_P2P_DISCOVERY_STOPPED + */ + public static final String EXTRA_DISCOVERY_STATE = "discoveryState"; + + /** + * p2p discovery has stopped + * + * @see #WIFI_P2P_DISCOVERY_CHANGED_ACTION + */ + public static final int WIFI_P2P_DISCOVERY_STOPPED = 1; + + /** + * p2p discovery has started + * + * @see #WIFI_P2P_DISCOVERY_CHANGED_ACTION + */ + public static final int WIFI_P2P_DISCOVERY_STARTED = 2; + + /** * Broadcast intent action indicating that this device details have changed. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -199,68 +270,116 @@ public class WifiP2pManager { private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER; /** @hide */ - public static final int ENABLE_P2P = BASE + 1; + public static final int DISCOVER_PEERS = BASE + 1; /** @hide */ - public static final int ENABLE_P2P_FAILED = BASE + 2; + public static final int DISCOVER_PEERS_FAILED = BASE + 2; /** @hide */ - public static final int ENABLE_P2P_SUCCEEDED = BASE + 3; + public static final int DISCOVER_PEERS_SUCCEEDED = BASE + 3; /** @hide */ - public static final int DISABLE_P2P = BASE + 4; + public static final int STOP_DISCOVERY = BASE + 4; /** @hide */ - public static final int DISABLE_P2P_FAILED = BASE + 5; + public static final int STOP_DISCOVERY_FAILED = BASE + 5; /** @hide */ - public static final int DISABLE_P2P_SUCCEEDED = BASE + 6; + public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 6; /** @hide */ - public static final int DISCOVER_PEERS = BASE + 7; + public static final int CONNECT = BASE + 7; /** @hide */ - public static final int DISCOVER_PEERS_FAILED = BASE + 8; + public static final int CONNECT_FAILED = BASE + 8; /** @hide */ - public static final int DISCOVER_PEERS_SUCCEEDED = BASE + 9; + public static final int CONNECT_SUCCEEDED = BASE + 9; /** @hide */ - public static final int CONNECT = BASE + 10; + public static final int CANCEL_CONNECT = BASE + 10; /** @hide */ - public static final int CONNECT_FAILED = BASE + 11; + public static final int CANCEL_CONNECT_FAILED = BASE + 11; /** @hide */ - public static final int CONNECT_SUCCEEDED = BASE + 12; + public static final int CANCEL_CONNECT_SUCCEEDED = BASE + 12; /** @hide */ - public static final int CANCEL_CONNECT = BASE + 13; + public static final int CREATE_GROUP = BASE + 13; /** @hide */ - public static final int CANCEL_CONNECT_FAILED = BASE + 14; + public static final int CREATE_GROUP_FAILED = BASE + 14; /** @hide */ - public static final int CANCEL_CONNECT_SUCCEEDED = BASE + 15; + public static final int CREATE_GROUP_SUCCEEDED = BASE + 15; /** @hide */ - public static final int CREATE_GROUP = BASE + 16; + public static final int REMOVE_GROUP = BASE + 16; /** @hide */ - public static final int CREATE_GROUP_FAILED = BASE + 17; + public static final int REMOVE_GROUP_FAILED = BASE + 17; /** @hide */ - public static final int CREATE_GROUP_SUCCEEDED = BASE + 18; + public static final int REMOVE_GROUP_SUCCEEDED = BASE + 18; /** @hide */ - public static final int REMOVE_GROUP = BASE + 19; + public static final int REQUEST_PEERS = BASE + 19; + /** @hide */ + public static final int RESPONSE_PEERS = BASE + 20; + /** @hide */ - public static final int REMOVE_GROUP_FAILED = BASE + 20; + public static final int REQUEST_CONNECTION_INFO = BASE + 21; /** @hide */ - public static final int REMOVE_GROUP_SUCCEEDED = BASE + 21; + public static final int RESPONSE_CONNECTION_INFO = BASE + 22; /** @hide */ - public static final int REQUEST_PEERS = BASE + 22; + public static final int REQUEST_GROUP_INFO = BASE + 23; /** @hide */ - public static final int RESPONSE_PEERS = BASE + 23; + public static final int RESPONSE_GROUP_INFO = BASE + 24; /** @hide */ - public static final int REQUEST_CONNECTION_INFO = BASE + 24; + public static final int ADD_LOCAL_SERVICE = BASE + 28; + /** @hide */ + public static final int ADD_LOCAL_SERVICE_FAILED = BASE + 29; /** @hide */ - public static final int RESPONSE_CONNECTION_INFO = BASE + 25; + public static final int ADD_LOCAL_SERVICE_SUCCEEDED = BASE + 30; /** @hide */ - public static final int REQUEST_GROUP_INFO = BASE + 26; + public static final int REMOVE_LOCAL_SERVICE = BASE + 31; + /** @hide */ + public static final int REMOVE_LOCAL_SERVICE_FAILED = BASE + 32; /** @hide */ - public static final int RESPONSE_GROUP_INFO = BASE + 27; + public static final int REMOVE_LOCAL_SERVICE_SUCCEEDED = BASE + 33; + + /** @hide */ + public static final int CLEAR_LOCAL_SERVICES = BASE + 34; + /** @hide */ + public static final int CLEAR_LOCAL_SERVICES_FAILED = BASE + 35; + /** @hide */ + public static final int CLEAR_LOCAL_SERVICES_SUCCEEDED = BASE + 36; + + /** @hide */ + public static final int ADD_SERVICE_REQUEST = BASE + 37; + /** @hide */ + public static final int ADD_SERVICE_REQUEST_FAILED = BASE + 38; + /** @hide */ + public static final int ADD_SERVICE_REQUEST_SUCCEEDED = BASE + 39; + + /** @hide */ + public static final int REMOVE_SERVICE_REQUEST = BASE + 40; + /** @hide */ + public static final int REMOVE_SERVICE_REQUEST_FAILED = BASE + 41; + /** @hide */ + public static final int REMOVE_SERVICE_REQUEST_SUCCEEDED = BASE + 42; + + /** @hide */ + public static final int CLEAR_SERVICE_REQUESTS = BASE + 43; + /** @hide */ + public static final int CLEAR_SERVICE_REQUESTS_FAILED = BASE + 44; + /** @hide */ + public static final int CLEAR_SERVICE_REQUESTS_SUCCEEDED = BASE + 45; + + /** @hide */ + public static final int DISCOVER_SERVICES = BASE + 46; + /** @hide */ + public static final int DISCOVER_SERVICES_FAILED = BASE + 47; + /** @hide */ + public static final int DISCOVER_SERVICES_SUCCEEDED = BASE + 48; + + /** @hide */ + public static final int PING = BASE + 49; + + /** @hide */ + public static final int RESPONSE_SERVICE = BASE + 50; /** * Create a new WifiP2pManager instance. Applications use @@ -293,6 +412,14 @@ public class WifiP2pManager { */ public static final int BUSY = 2; + /** + * Passed with {@link ActionListener#onFailure}. + * Indicates that the {@link #discoverServices} failed because no service + * requests are set. + * @hide + */ + public static final int NO_SERVICE_REQUESTS = 3; + /** Interface for callback invocation when framework channel is lost */ public interface ChannelListener { /** @@ -342,6 +469,89 @@ public class WifiP2pManager { } /** + * Interface for callback invocation when service discovery response other than + * Upnp or Bonjour is received + */ + public interface ServiceResponseListener { + + /** + * The requested service response is available. + * + * @param protocolType protocol type. currently only + * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}. + * @param responseData service discovery response data based on the requested + * service protocol type. The format depends on the service type. + * @param srcDevice source device. + */ + public void onServiceAvailable(int protocolType, + byte[] responseData, WifiP2pDevice srcDevice); + } + + /** + * Interface for callback invocation when Bonjour service discovery response + * is received + */ + public interface DnsSdServiceResponseListener { + + /** + * The requested Bonjour service response is available. + * + * <p>This function is invoked when the device with the specified Bonjour + * registration type returned the instance name. + * @param instanceName instance name.<br> + * e.g) "MyPrinter". + * @param registrationType <br> + * e.g) "_ipp._tcp.local." + * @param srcDevice source device. + */ + public void onDnsSdServiceAvailable(String instanceName, + String registrationType, WifiP2pDevice srcDevice); + + } + + /** + * Interface for callback invocation when Bonjour TXT record is available + * for a service + */ + public interface DnsSdTxtRecordListener { + /** + * The requested Bonjour service response is available. + * + * <p>This function is invoked when the device with the specified full + * service domain service returned TXT record. + * + * @param fullDomainName full domain name. <br> + * e.g) "MyPrinter._ipp._tcp.local.". + * @param record txt record. + * @param srcDevice source device. + */ + public void onDnsSdTxtRecordAvailable(String fullDomainName, + DnsSdTxtRecord record, + WifiP2pDevice srcDevice); + } + + /** + * Interface for callback invocation when upnp service discovery response + * is received + * */ + public interface UpnpServiceResponseListener { + + /** + * The requested upnp service response is available. + * + * <p>This function is invoked when the specified device or service is found. + * + * @param uniqueServiceNames The list of unique service names.<br> + * e.g) uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device: + * MediaServer:1 + * @param srcDevice source device. + */ + public void onUpnpServiceAvailable(List<String> uniqueServiceNames, + WifiP2pDevice srcDevice); + } + + + /** * A channel that connects the application to the Wifi p2p framework. * Most p2p operations require a Channel as an argument. An instance of Channel is obtained * by doing a call on {@link #initialize} @@ -352,7 +562,12 @@ public class WifiP2pManager { mHandler = new P2pHandler(looper); mChannelListener = l; } + private final static int INVALID_LISTENER_KEY = 0; private ChannelListener mChannelListener; + private ServiceResponseListener mServRspListener; + private DnsSdServiceResponseListener mDnsSdServRspListener; + private DnsSdTxtRecordListener mDnsSdTxtListener; + private UpnpServiceResponseListener mUpnpServRspListener; private HashMap<Integer, Object> mListenerMap = new HashMap<Integer, Object>(); private Object mListenerMapLock = new Object(); private int mListenerKey = 0; @@ -376,20 +591,36 @@ public class WifiP2pManager { break; /* ActionListeners grouped together */ case WifiP2pManager.DISCOVER_PEERS_FAILED: + case WifiP2pManager.STOP_DISCOVERY_FAILED: + case WifiP2pManager.DISCOVER_SERVICES_FAILED: case WifiP2pManager.CONNECT_FAILED: case WifiP2pManager.CANCEL_CONNECT_FAILED: case WifiP2pManager.CREATE_GROUP_FAILED: case WifiP2pManager.REMOVE_GROUP_FAILED: + case WifiP2pManager.ADD_LOCAL_SERVICE_FAILED: + case WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED: + case WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED: + case WifiP2pManager.ADD_SERVICE_REQUEST_FAILED: + case WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED: + case WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED: if (listener != null) { ((ActionListener) listener).onFailure(message.arg1); } break; /* ActionListeners grouped together */ case WifiP2pManager.DISCOVER_PEERS_SUCCEEDED: + case WifiP2pManager.STOP_DISCOVERY_SUCCEEDED: + case WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED: case WifiP2pManager.CONNECT_SUCCEEDED: case WifiP2pManager.CANCEL_CONNECT_SUCCEEDED: case WifiP2pManager.CREATE_GROUP_SUCCEEDED: case WifiP2pManager.REMOVE_GROUP_SUCCEEDED: + case WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED: + case WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED: + case WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED: + case WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED: + case WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED: + case WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED: if (listener != null) { ((ActionListener) listener).onSuccess(); } @@ -412,6 +643,10 @@ public class WifiP2pManager { ((GroupInfoListener) listener).onGroupInfoAvailable(group); } break; + case WifiP2pManager.RESPONSE_SERVICE: + WifiP2pServiceResponse resp = (WifiP2pServiceResponse) message.obj; + handleServiceResponse(resp); + break; default: Log.d(TAG, "Ignored " + message); break; @@ -419,23 +654,78 @@ public class WifiP2pManager { } } - int putListener(Object listener) { - if (listener == null) return 0; + private void handleServiceResponse(WifiP2pServiceResponse resp) { + if (resp instanceof WifiP2pDnsSdServiceResponse) { + handleDnsSdServiceResponse((WifiP2pDnsSdServiceResponse)resp); + } else if (resp instanceof WifiP2pUpnpServiceResponse) { + if (mUpnpServRspListener != null) { + handleUpnpServiceResponse((WifiP2pUpnpServiceResponse)resp); + } + } else { + if (mServRspListener != null) { + mServRspListener.onServiceAvailable(resp.getServiceType(), + resp.getRawData(), resp.getSrcDevice()); + } + } + } + + private void handleUpnpServiceResponse(WifiP2pUpnpServiceResponse resp) { + mUpnpServRspListener.onUpnpServiceAvailable(resp.getUniqueServiceNames(), + resp.getSrcDevice()); + } + + private void handleDnsSdServiceResponse(WifiP2pDnsSdServiceResponse resp) { + if (resp.getDnsType() == WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR) { + if (mDnsSdServRspListener != null) { + mDnsSdServRspListener.onDnsSdServiceAvailable( + resp.getInstanceName(), + resp.getDnsQueryName(), + resp.getSrcDevice()); + } + } else if (resp.getDnsType() == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) { + if (mDnsSdTxtListener != null) { + mDnsSdTxtListener.onDnsSdTxtRecordAvailable( + resp.getDnsQueryName(), + resp.getTxtRecord(), + resp.getSrcDevice()); + } + } else { + Log.e(TAG, "Unhandled resp " + resp); + } + } + + private int putListener(Object listener) { + if (listener == null) return INVALID_LISTENER_KEY; int key; synchronized (mListenerMapLock) { - key = mListenerKey++; + do { + key = mListenerKey++; + } while (key == INVALID_LISTENER_KEY); mListenerMap.put(key, listener); } return key; } - Object getListener(int key) { + private Object getListener(int key) { + if (key == INVALID_LISTENER_KEY) return null; synchronized (mListenerMapLock) { return mListenerMap.remove(key); } } } + private static void checkChannel(Channel c) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + } + + private static void checkServiceInfo(WifiP2pServiceInfo info) { + if (info == null) throw new IllegalArgumentException("service info is null"); + } + + private static void checkServiceRequest(WifiP2pServiceRequest req) { + if (req == null) throw new IllegalArgumentException("service request is null"); + } + /** * Registers the application with the Wi-Fi framework. This function * must be the first to be called before any p2p operations are performed. @@ -459,26 +749,6 @@ public class WifiP2pManager { } /** - * Sends in a request to the system to enable p2p. This will pop up a dialog - * to the user and upon authorization will enable p2p. - * @hide - */ - public void enableP2p(Channel c) { - if (c == null) return; - c.mAsyncChannel.sendMessage(ENABLE_P2P); - } - - /** - * Sends in a request to the system to disable p2p. This will pop up a dialog - * to the user and upon authorization will enable p2p. - * @hide - */ - public void disableP2p(Channel c) { - if (c == null) return; - c.mAsyncChannel.sendMessage(DISABLE_P2P); - } - - /** * Initiate peer discovery. A discovery process involves scanning for available Wi-Fi peers * for the purpose of establishing a connection. * @@ -498,10 +768,26 @@ public class WifiP2pManager { * @param listener for callbacks on success or failure. Can be null. */ public void discoverPeers(Channel c, ActionListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener)); } + /** + * Stop an ongoing peer discovery + * + * <p> The function call immediately returns after sending a stop request + * to the framework. The application is notified of a success or failure to initiate + * stop through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * @param c is the channel created at {@link #initialize} + * @param listener for callbacks on success or failure. Can be null. + */ + public void stopPeerDiscovery(Channel c, ActionListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, c.putListener(listener)); + } + /** * Start a p2p connection to a device with the specified configuration. * @@ -525,7 +811,7 @@ public class WifiP2pManager { * @param listener for callbacks on success or failure. Can be null. */ public void connect(Channel c, WifiP2pConfig config, ActionListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(CONNECT, 0, c.putListener(listener), config); } @@ -541,7 +827,7 @@ public class WifiP2pManager { * @param listener for callbacks on success or failure. Can be null. */ public void cancelConnect(Channel c, ActionListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(CANCEL_CONNECT, 0, c.putListener(listener)); } @@ -565,7 +851,7 @@ public class WifiP2pManager { * @param listener for callbacks on success or failure. Can be null. */ public void createGroup(Channel c, ActionListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(CREATE_GROUP, 0, c.putListener(listener)); } @@ -581,18 +867,217 @@ public class WifiP2pManager { * @param listener for callbacks on success or failure. Can be null. */ public void removeGroup(Channel c, ActionListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(REMOVE_GROUP, 0, c.putListener(listener)); } /** + * Register a local service for service discovery. If a local service is registered, + * the framework automatically responds to a service discovery request from a peer. + * + * <p> The function call immediately returns after sending a request to add a local + * service to the framework. The application is notified of a success or failure to + * add service through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * <p>The service information is set through {@link WifiP2pServiceInfo}.<br> + * or its subclass calls {@link WifiP2pUpnpServiceInfo#newInstance} or + * {@link WifiP2pDnsSdServiceInfo#newInstance} for a Upnp or Bonjour service + * respectively + * + * <p>The service information can be cleared with calls to + * {@link #removeLocalService} or {@link #clearLocalServices}. + * + * @param c is the channel created at {@link #initialize} + * @param servInfo is a local service information. + * @param listener for callbacks on success or failure. Can be null. + */ + public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) { + checkChannel(c); + checkServiceInfo(servInfo); + c.mAsyncChannel.sendMessage(ADD_LOCAL_SERVICE, 0, c.putListener(listener), servInfo); + } + + /** + * Remove a registered local service added with {@link #addLocalService} + * + * <p> The function call immediately returns after sending a request to remove a + * local service to the framework. The application is notified of a success or failure to + * add service through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * @param c is the channel created at {@link #initialize} + * @param servInfo is the local service information. + * @param listener for callbacks on success or failure. Can be null. + */ + public void removeLocalService(Channel c, WifiP2pServiceInfo servInfo, + ActionListener listener) { + checkChannel(c); + checkServiceInfo(servInfo); + c.mAsyncChannel.sendMessage(REMOVE_LOCAL_SERVICE, 0, c.putListener(listener), servInfo); + } + + /** + * Clear all registered local services of service discovery. + * + * <p> The function call immediately returns after sending a request to clear all + * local services to the framework. The application is notified of a success or failure to + * add service through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * @param c is the channel created at {@link #initialize} + * @param listener for callbacks on success or failure. Can be null. + */ + public void clearLocalServices(Channel c, ActionListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(CLEAR_LOCAL_SERVICES, 0, c.putListener(listener)); + } + + /** + * Register a callback to be invoked on receiving service discovery response. + * Used only for vendor specific protocol right now. For Bonjour or Upnp, use + * {@link #setDnsSdResponseListeners} or {@link #setUpnpServiceResponseListener} + * respectively. + * + * <p> see {@link #discoverServices} for the detail. + * + * @param c is the channel created at {@link #initialize} + * @param listener for callbacks on receiving service discovery response. + */ + public void setServiceResponseListener(Channel c, + ServiceResponseListener listener) { + checkChannel(c); + c.mServRspListener = listener; + } + + /** + * Register a callback to be invoked on receiving Bonjour service discovery + * response. + * + * <p> see {@link #discoverServices} for the detail. + * + * @param c + * @param servListener is for listening to a Bonjour service response + * @param txtListener is for listening to a Bonjour TXT record response + */ + public void setDnsSdResponseListeners(Channel c, + DnsSdServiceResponseListener servListener, DnsSdTxtRecordListener txtListener) { + checkChannel(c); + c.mDnsSdServRspListener = servListener; + c.mDnsSdTxtListener = txtListener; + } + + /** + * Register a callback to be invoked on receiving upnp service discovery + * response. + * + * <p> see {@link #discoverServices} for the detail. + * + * @param c is the channel created at {@link #initialize} + * @param listener for callbacks on receiving service discovery response. + */ + public void setUpnpServiceResponseListener(Channel c, + UpnpServiceResponseListener listener) { + checkChannel(c); + c.mUpnpServRspListener = listener; + } + + /** + * Initiate service discovery. A discovery process involves scanning for + * requested services for the purpose of establishing a connection to a peer + * that supports an available service. + * + * <p> The function call immediately returns after sending a request to start service + * discovery to the framework. The application is notified of a success or failure to initiate + * discovery through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * <p> The services to be discovered are specified with calls to {@link #addServiceRequest}. + * + * <p>The application is notified of the response against the service discovery request + * through listener callbacks registered by {@link #setServiceResponseListener} or + * {@link #setDnsSdResponseListeners}, or {@link #setUpnpServiceResponseListener}. + * + * @param c is the channel created at {@link #initialize} + * @param listener for callbacks on success or failure. Can be null. + */ + public void discoverServices(Channel c, ActionListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener)); + } + + /** + * Add a service discovery request. + * + * <p> The function call immediately returns after sending a request to add service + * discovery request to the framework. The application is notified of a success or failure to + * add service through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * <p>After service discovery request is added, you can initiate service discovery by + * {@link #discoverServices}. + * + * <p>The added service requests can be cleared with calls to + * {@link #removeServiceRequest(Channel, WifiP2pServiceRequest, ActionListener)} or + * {@link #clearServiceRequests(Channel, ActionListener)}. + * + * @param c is the channel created at {@link #initialize} + * @param req is the service discovery request. + * @param listener for callbacks on success or failure. Can be null. + */ + public void addServiceRequest(Channel c, + WifiP2pServiceRequest req, ActionListener listener) { + checkChannel(c); + checkServiceRequest(req); + c.mAsyncChannel.sendMessage(ADD_SERVICE_REQUEST, 0, + c.putListener(listener), req); + } + + /** + * Remove a specified service discovery request added with {@link #addServiceRequest} + * + * <p> The function call immediately returns after sending a request to remove service + * discovery request to the framework. The application is notified of a success or failure to + * add service through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * @param c is the channel created at {@link #initialize} + * @param req is the service discovery request. + * @param listener for callbacks on success or failure. Can be null. + */ + public void removeServiceRequest(Channel c, WifiP2pServiceRequest req, + ActionListener listener) { + checkChannel(c); + checkServiceRequest(req); + c.mAsyncChannel.sendMessage(REMOVE_SERVICE_REQUEST, 0, + c.putListener(listener), req); + } + + /** + * Clear all registered service discovery requests. + * + * <p> The function call immediately returns after sending a request to clear all + * service discovery requests to the framework. The application is notified of a success + * or failure to add service through listener callbacks {@link ActionListener#onSuccess} or + * {@link ActionListener#onFailure}. + * + * @param c is the channel created at {@link #initialize} + * @param listener for callbacks on success or failure. Can be null. + */ + public void clearServiceRequests(Channel c, ActionListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(CLEAR_SERVICE_REQUESTS, + 0, c.putListener(listener)); + } + + /** * Request the current list of peers. * * @param c is the channel created at {@link #initialize} * @param listener for callback when peer list is available. Can be null. */ public void requestPeers(Channel c, PeerListListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener)); } @@ -603,7 +1088,7 @@ public class WifiP2pManager { * @param listener for callback when connection info is available. Can be null. */ public void requestConnectionInfo(Channel c, ConnectionInfoListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(REQUEST_CONNECTION_INFO, 0, c.putListener(listener)); } @@ -614,7 +1099,7 @@ public class WifiP2pManager { * @param listener for callback when group info is available. Can be null. */ public void requestGroupInfo(Channel c, GroupInfoListener listener) { - if (c == null) return; + checkChannel(c); c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener)); } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pProvDiscEvent.java b/wifi/java/android/net/wifi/p2p/WifiP2pProvDiscEvent.java new file mode 100644 index 0000000..f70abe8 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pProvDiscEvent.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p; + +import android.os.Parcelable; +import android.os.Parcel; +import android.util.Log; + +/** + * A class representing a Wi-Fi p2p provisional discovery request/response + * See {@link #WifiP2pProvDiscEvent} for supported types + * + * @hide + */ +public class WifiP2pProvDiscEvent { + + private static final String TAG = "WifiP2pProvDiscEvent"; + + public static final int PBC_REQ = 1; + public static final int PBC_RSP = 2; + public static final int ENTER_PIN = 3; + public static final int SHOW_PIN = 4; + + /* One of PBC_REQ, PBC_RSP, ENTER_PIN or SHOW_PIN */ + public int event; + + public WifiP2pDevice device; + + /* Valid when event = SHOW_PIN */ + public String pin; + + public WifiP2pProvDiscEvent() { + device = new WifiP2pDevice(); + } + + /** + * @param string formats supported include + * + * P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 + * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + * P2P-PROV-DISC-PBC-RESP 02:12:47:f2:5a:36 + * + * P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 + * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + * P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607 p2p_dev_addr=42:fc:89:e1:e2:27 + * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + * Note: The events formats can be looked up in the wpa_supplicant code + * @hide + */ + public WifiP2pProvDiscEvent(String string) throws IllegalArgumentException { + String[] tokens = string.split(" "); + + if (tokens.length < 2) { + throw new IllegalArgumentException("Malformed event " + string); + } + + if (tokens[0].endsWith("PBC-REQ")) event = PBC_REQ; + else if (tokens[0].endsWith("PBC-RESP")) event = PBC_RSP; + else if (tokens[0].endsWith("ENTER-PIN")) event = ENTER_PIN; + else if (tokens[0].endsWith("SHOW-PIN")) event = SHOW_PIN; + else throw new IllegalArgumentException("Malformed event " + string); + + device = new WifiP2pDevice(); + + for (String token : tokens) { + String[] nameValue = token.split("="); + if (nameValue.length != 2) { + //mac address without key is device address + if (token.matches("(([0-9a-f]{2}:){5}[0-9a-f]{2})")) { + device.deviceAddress = token; + } else if (token.matches("[0-9]+")) { + pin = token; + } else { + //ignore; + } + continue; + } + + if (nameValue[0].equals("p2p_dev_addr")) { + device.deviceAddress = nameValue[1]; + continue; + } + + if (nameValue[0].equals("pri_dev_type")) { + device.primaryDeviceType = nameValue[1]; + continue; + } + + if (nameValue[0].equals("name")) { + device.deviceName = trimQuotes(nameValue[1]); + continue; + } + + if (nameValue[0].equals("config_methods")) { + device.wpsConfigMethodsSupported = parseHex(nameValue[1]); + continue; + } + + if (nameValue[0].equals("dev_capab")) { + device.deviceCapability = parseHex(nameValue[1]); + continue; + } + + if (nameValue[0].equals("group_capab")) { + device.groupCapability = parseHex(nameValue[1]); + continue; + } + } + } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append(device); + sbuf.append("\n event: ").append(event); + sbuf.append("\n pin: ").append(pin); + return sbuf.toString(); + } + + private String trimQuotes(String str) { + str = str.trim(); + if (str.startsWith("'") && str.endsWith("'")) { + return str.substring(1, str.length()-1); + } + return str; + } + + //supported formats: 0x1abc, 0X1abc, 1abc + private int parseHex(String hexString) { + int num = 0; + if (hexString.startsWith("0x") || hexString.startsWith("0X")) { + hexString = hexString.substring(2); + } + + try { + num = Integer.parseInt(hexString, 16); + } catch(NumberFormatException e) { + Log.e(TAG, "Failed to parse hex string " + hexString); + } + return num; + } +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java index 6bb22a4..6168f0e 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java @@ -42,6 +42,9 @@ import android.net.wifi.WifiMonitor; import android.net.wifi.WifiNative; import android.net.wifi.WifiStateMachine; import android.net.wifi.WpsInfo; +import android.net.wifi.p2p.nsd.WifiP2pServiceInfo; +import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; +import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; import android.os.Binder; import android.os.IBinder; import android.os.INetworkManagementService; @@ -49,14 +52,19 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.Messenger; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; +import android.text.TextUtils; import android.util.Slog; +import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.widget.EditText; +import android.widget.TextView; import com.android.internal.R; import com.android.internal.telephony.TelephonyIntents; @@ -67,7 +75,10 @@ import com.android.internal.util.StateMachine; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.List; /** * WifiP2pService inclues a state machine to perform Wi-Fi p2p operations. Applications @@ -91,18 +102,22 @@ public class WifiP2pService extends IWifiP2pManager.Stub { INetworkManagementService mNwService; private DhcpStateMachine mDhcpStateMachine; - //Tracked to notify the user about wifi client/hotspot being shut down - //during p2p bring up - private int mWifiState = WifiManager.WIFI_STATE_DISABLED; - private int mWifiApState = WifiManager.WIFI_AP_STATE_DISABLED; - private P2pStateMachine mP2pStateMachine; private AsyncChannel mReplyChannel = new AsyncChannel(); private AsyncChannel mWifiChannel; + private static final Boolean JOIN_GROUP = true; + private static final Boolean FORM_GROUP = false; + /* Two minutes comes from the wpa_supplicant setting */ - private static final int GROUP_NEGOTIATION_WAIT_TIME_MS = 120 * 1000; - private static int mGroupNegotiationTimeoutIndex = 0; + private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000; + private static int mGroupCreatingTimeoutIndex = 0; + + /* Set a two minute discover timeout to avoid STA scans from being blocked */ + private static final int DISCOVER_TIMEOUT_S = 120; + + /* Idle time after a peer is gone when the group is torn down */ + private static final int GROUP_IDLE_TIME_S = 2; /** * Delay between restarts upon failure to setup connection with supplicant @@ -118,35 +133,13 @@ public class WifiP2pService extends IWifiP2pManager.Stub { private static final int BASE = Protocol.BASE_WIFI_P2P_SERVICE; - /* Message sent to WifiStateMachine to indicate p2p enable is pending */ - public static final int P2P_ENABLE_PENDING = BASE + 1; - /* Message sent to WifiStateMachine to indicate Wi-Fi client/hotspot operation can proceed */ - public static final int WIFI_ENABLE_PROCEED = BASE + 2; - - /* Delayed message to timeout of group negotiation */ - public static final int GROUP_NEGOTIATION_TIMED_OUT = BASE + 3; - - /* User accepted to disable Wi-Fi in order to enable p2p */ - private static final int WIFI_DISABLE_USER_ACCEPT = BASE + 4; - /* User rejected to disable Wi-Fi in order to enable p2p */ - private static final int WIFI_DISABLE_USER_REJECT = BASE + 5; - - /* User accepted a group negotiation request */ - private static final int GROUP_NEGOTIATION_USER_ACCEPT = BASE + 6; - /* User rejected a group negotiation request */ - private static final int GROUP_NEGOTIATION_USER_REJECT = BASE + 7; - - /* User accepted a group invitation request */ - private static final int GROUP_INVITATION_USER_ACCEPT = BASE + 8; - /* User rejected a group invitation request */ - private static final int GROUP_INVITATION_USER_REJECT = BASE + 9; + /* Delayed message to timeout group creation */ + public static final int GROUP_CREATING_TIMED_OUT = BASE + 1; - /* Airplane mode changed */ - private static final int AIRPLANE_MODE_CHANGED = BASE + 10; - /* Emergency callback mode */ - private static final int EMERGENCY_CALLBACK_MODE = BASE + 11; - private static final int WPS_PBC = BASE + 12; - private static final int WPS_PIN = BASE + 13; + /* User accepted a peer request */ + private static final int PEER_CONNECTION_USER_ACCEPT = BASE + 2; + /* User rejected a peer request */ + private static final int PEER_CONNECTION_USER_REJECT = BASE + 3; private final boolean mP2pSupported; @@ -157,8 +150,23 @@ public class WifiP2pService extends IWifiP2pManager.Stub { * is invoked */ private boolean mPersistGroup; + /* Track whether we are in p2p discovery. This is used to avoid sending duplicate + * broadcasts + */ + private boolean mDiscoveryStarted; + private NetworkInfo mNetworkInfo; + /* The transaction Id of service discovery request */ + private byte mServiceTransactionId = 0; + + /* Service discovery request ID of wpa_supplicant. + * null means it's not set yet. */ + private String mServiceDiscReqId; + + /* clients(application) information list. */ + private HashMap<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>(); + /* Is chosen as a unique range to avoid conflict with the range defined in Tethering.java */ private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"}; @@ -167,7 +175,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub { public WifiP2pService(Context context) { mContext = context; - mInterface = SystemProperties.get("wifi.interface", "wlan0"); + //STOPSHIP: get this from native side + mInterface = "p2p0"; mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, ""); mP2pSupported = mContext.getPackageManager().hasSystemFeature( @@ -179,15 +188,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported); mP2pStateMachine.start(); - - // broadcasts - IntentFilter filter = new IntentFilter(); - filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); - filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); - filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - mContext.registerReceiver(new WifiStateReceiver(), filter); - } public void connectivityServiceReady() { @@ -195,26 +195,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mNwService = INetworkManagementService.Stub.asInterface(b); } - private class WifiStateReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_DISABLED); - } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { - mWifiApState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, - WifiManager.WIFI_AP_STATE_DISABLED); - } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { - mP2pStateMachine.sendMessage(AIRPLANE_MODE_CHANGED); - } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) { - if (intent.getBooleanExtra("phoneinECMState", false) == true) { - mP2pStateMachine.sendMessage(EMERGENCY_CALLBACK_MODE); - } - } - } - } - private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, "WifiP2pService"); @@ -264,30 +244,28 @@ public class WifiP2pService extends IWifiP2pManager.Stub { private P2pNotSupportedState mP2pNotSupportedState = new P2pNotSupportedState(); private P2pDisablingState mP2pDisablingState = new P2pDisablingState(); private P2pDisabledState mP2pDisabledState = new P2pDisabledState(); - private WaitForUserActionState mWaitForUserActionState = new WaitForUserActionState(); - private WaitForWifiDisableState mWaitForWifiDisableState = new WaitForWifiDisableState(); private P2pEnablingState mP2pEnablingState = new P2pEnablingState(); private P2pEnabledState mP2pEnabledState = new P2pEnabledState(); // Inactive is when p2p is enabled with no connectivity private InactiveState mInactiveState = new InactiveState(); - private UserAuthorizingGroupNegotiationState mUserAuthorizingGroupNegotiationState - = new UserAuthorizingGroupNegotiationState(); - private UserAuthorizingGroupInvitationState mUserAuthorizingGroupInvitationState - = new UserAuthorizingGroupInvitationState(); + private GroupCreatingState mGroupCreatingState = new GroupCreatingState(); + private UserAuthorizingInvitationState mUserAuthorizingInvitationState + = new UserAuthorizingInvitationState(); + private ProvisionDiscoveryState mProvisionDiscoveryState = new ProvisionDiscoveryState(); private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState(); + private GroupCreatedState mGroupCreatedState = new GroupCreatedState(); + private UserAuthorizingJoinState mUserAuthorizingJoinState = new UserAuthorizingJoinState(); - private WifiMonitor mWifiMonitor = new WifiMonitor(this); + private WifiNative mWifiNative = new WifiNative(mInterface); + private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative); private WifiP2pDeviceList mPeers = new WifiP2pDeviceList(); private WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo(); private WifiP2pGroup mGroup; - // Saved WifiP2pConfig from GO negotiation request - private WifiP2pConfig mSavedGoNegotiationConfig; - - // Saved WifiP2pConfig from connect request - private WifiP2pConfig mSavedConnectConfig; + // Saved WifiP2pConfig for a peer connection + private WifiP2pConfig mSavedPeerConfig; // Saved WifiP2pGroup from invitation request private WifiP2pGroup mSavedP2pGroup; @@ -299,15 +277,15 @@ public class WifiP2pService extends IWifiP2pManager.Stub { addState(mP2pNotSupportedState, mDefaultState); addState(mP2pDisablingState, mDefaultState); addState(mP2pDisabledState, mDefaultState); - addState(mWaitForUserActionState, mP2pDisabledState); - addState(mWaitForWifiDisableState, mP2pDisabledState); addState(mP2pEnablingState, mDefaultState); addState(mP2pEnabledState, mDefaultState); addState(mInactiveState, mP2pEnabledState); - addState(mUserAuthorizingGroupNegotiationState, mInactiveState); - addState(mUserAuthorizingGroupInvitationState, mInactiveState); - addState(mGroupNegotiationState, mP2pEnabledState); + addState(mGroupCreatingState, mP2pEnabledState); + addState(mUserAuthorizingInvitationState, mGroupCreatingState); + addState(mProvisionDiscoveryState, mGroupCreatingState); + addState(mGroupNegotiationState, mGroupCreatingState); addState(mGroupCreatedState, mP2pEnabledState); + addState(mUserAuthorizingJoinState, mGroupCreatedState); if (p2pSupported) { setInitialState(mP2pDisabledState); @@ -344,21 +322,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub { AsyncChannel ac = new AsyncChannel(); ac.connect(mContext, getHandler(), message.replyTo); break; - case WifiStateMachine.WIFI_ENABLE_PENDING: - // Disable p2p operation before we can respond - sendMessage(WifiP2pManager.DISABLE_P2P); - deferMessage(message); - break; - case WifiP2pManager.ENABLE_P2P: - replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED, + case WifiP2pManager.DISCOVER_PEERS: + replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, WifiP2pManager.BUSY); break; - case WifiP2pManager.DISABLE_P2P: - replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED, + case WifiP2pManager.STOP_DISCOVERY: + replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED, WifiP2pManager.BUSY); break; - case WifiP2pManager.DISCOVER_PEERS: - replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, + case WifiP2pManager.DISCOVER_SERVICES: + replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.CONNECT: @@ -377,6 +350,32 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED, WifiP2pManager.BUSY); break; + case WifiP2pManager.ADD_LOCAL_SERVICE: + replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED, + WifiP2pManager.BUSY); + break; + case WifiP2pManager.REMOVE_LOCAL_SERVICE: + replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED, + WifiP2pManager.BUSY); + break; + case WifiP2pManager.CLEAR_LOCAL_SERVICES: + replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED, + WifiP2pManager.BUSY); + break; + case WifiP2pManager.ADD_SERVICE_REQUEST: + replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED, + WifiP2pManager.BUSY); + break; + case WifiP2pManager.REMOVE_SERVICE_REQUEST: + replyToMessage(message, + WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED, + WifiP2pManager.BUSY); + break; + case WifiP2pManager.CLEAR_SERVICE_REQUESTS: + replyToMessage(message, + WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED, + WifiP2pManager.BUSY); + break; case WifiP2pManager.REQUEST_PEERS: replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers); break; @@ -386,20 +385,28 @@ public class WifiP2pService extends IWifiP2pManager.Stub { case WifiP2pManager.REQUEST_GROUP_INFO: replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, mGroup); break; - case AIRPLANE_MODE_CHANGED: - if (isAirplaneModeOn()) sendMessage(WifiP2pManager.DISABLE_P2P); + // Ignore + case WifiMonitor.P2P_INVITATION_RESULT_EVENT: + case WifiMonitor.SCAN_RESULTS_EVENT: + case WifiMonitor.SUP_CONNECTION_EVENT: + case WifiMonitor.SUP_DISCONNECTION_EVENT: + case WifiMonitor.NETWORK_CONNECTION_EVENT: + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.P2P_GROUP_REMOVED_EVENT: + case PEER_CONNECTION_USER_ACCEPT: + case PEER_CONNECTION_USER_REJECT: + case GROUP_CREATING_TIMED_OUT: break; - case EMERGENCY_CALLBACK_MODE: - sendMessage(WifiP2pManager.DISABLE_P2P); + /* unexpected group created, remove */ + case WifiMonitor.P2P_GROUP_STARTED_EVENT: + mGroup = (WifiP2pGroup) message.obj; + loge("Unexpected group creation, remove " + mGroup); + mWifiNative.p2pGroupRemove(mGroup.getInterface()); break; - // Ignore - case WIFI_DISABLE_USER_ACCEPT: - case WIFI_DISABLE_USER_REJECT: - case GROUP_NEGOTIATION_USER_ACCEPT: - case GROUP_NEGOTIATION_USER_REJECT: - case GROUP_INVITATION_USER_ACCEPT: - case GROUP_INVITATION_USER_REJECT: - case GROUP_NEGOTIATION_TIMED_OUT: + case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT: + loge("Unexpected group failure, flush peers"); + mWifiNative.p2pFlush(); break; default: loge("Unhandled message " + message); @@ -413,20 +420,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub { @Override public boolean processMessage(Message message) { switch (message.what) { - // Allow Wi-Fi to proceed - case WifiStateMachine.WIFI_ENABLE_PENDING: - replyToMessage(message, WIFI_ENABLE_PROCEED); - break; - case WifiP2pManager.ENABLE_P2P: - replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED, + case WifiP2pManager.DISCOVER_PEERS: + replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; - case WifiP2pManager.DISABLE_P2P: - replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED, + case WifiP2pManager.STOP_DISCOVERY: + replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; - case WifiP2pManager.DISCOVER_PEERS: - replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, + case WifiP2pManager.DISCOVER_SERVICES: + replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.CONNECT: @@ -437,7 +440,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; - case WifiP2pManager.CREATE_GROUP: + case WifiP2pManager.CREATE_GROUP: replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; @@ -445,120 +448,50 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class P2pDisablingState extends State { - @Override - public void enter() { - if (DBG) logd(getName()); - logd("stopping supplicant"); - if (!WifiNative.stopSupplicant()) { - loge("Failed to stop supplicant, issue kill"); - WifiNative.killSupplicant(); - } - } - - @Override - public boolean processMessage(Message message) { - if (DBG) logd(getName() + message.toString()); - switch (message.what) { - case WifiMonitor.SUP_DISCONNECTION_EVENT: - logd("Supplicant connection lost"); - WifiNative.closeSupplicantConnection(); - transitionTo(mP2pDisabledState); + case WifiP2pManager.ADD_LOCAL_SERVICE: + replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); break; - case WifiP2pManager.ENABLE_P2P: - case WifiP2pManager.DISABLE_P2P: - deferMessage(message); + case WifiP2pManager.REMOVE_LOCAL_SERVICE: + replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - - class P2pDisabledState extends State { - @Override - public void enter() { - if (DBG) logd(getName()); - } - - @Override - public boolean processMessage(Message message) { - if (DBG) logd(getName() + message.toString()); - switch (message.what) { - case WifiP2pManager.ENABLE_P2P: - OnClickListener listener = new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - sendMessage(WIFI_DISABLE_USER_ACCEPT); - } else { - sendMessage(WIFI_DISABLE_USER_REJECT); - } - } - }; - - // Show a user request dialog if we know Wi-Fi client/hotspot is in operation - if (mWifiState != WifiManager.WIFI_STATE_DISABLED || - mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED) { - Resources r = Resources.getSystem(); - AlertDialog dialog = new AlertDialog.Builder(mContext) - .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) - .setMessage(r.getString(R.string.wifi_p2p_turnon_message)) - .setPositiveButton(r.getString(R.string.ok), listener) - .setNegativeButton(r.getString(R.string.cancel), listener) - .create(); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - dialog.show(); - transitionTo(mWaitForUserActionState); - } else { - mWifiChannel.sendMessage(P2P_ENABLE_PENDING); - transitionTo(mWaitForWifiDisableState); - } - replyToMessage(message, WifiP2pManager.ENABLE_P2P_SUCCEEDED); + case WifiP2pManager.CLEAR_LOCAL_SERVICES: + replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); break; - case WifiP2pManager.DISABLE_P2P: - replyToMessage(message, WifiP2pManager.DISABLE_P2P_SUCCEEDED); + case WifiP2pManager.ADD_SERVICE_REQUEST: + replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); break; - case WifiStateMachine.WIFI_ENABLE_PENDING: - replyToMessage(message, WIFI_ENABLE_PROCEED); + case WifiP2pManager.REMOVE_SERVICE_REQUEST: + replyToMessage(message, + WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); break; - default: + case WifiP2pManager.CLEAR_SERVICE_REQUESTS: + replyToMessage(message, + WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); + break; + default: return NOT_HANDLED; } return HANDLED; } } - class WaitForUserActionState extends State { - @Override - public void enter() { - if (DBG) logd(getName()); - } - + class P2pDisablingState extends State { @Override public boolean processMessage(Message message) { if (DBG) logd(getName() + message.toString()); switch (message.what) { - case WIFI_DISABLE_USER_ACCEPT: - mWifiChannel.sendMessage(P2P_ENABLE_PENDING); - transitionTo(mWaitForWifiDisableState); - break; - case WIFI_DISABLE_USER_REJECT: - logd("User rejected enabling p2p"); - sendP2pStateChangedBroadcast(false); + case WifiMonitor.SUP_DISCONNECTION_EVENT: + if (DBG) logd("p2p socket connection lost"); transitionTo(mP2pDisabledState); break; - case WifiP2pManager.ENABLE_P2P: - case WifiP2pManager.DISABLE_P2P: + case WifiStateMachine.CMD_ENABLE_P2P: + case WifiStateMachine.CMD_DISABLE_P2P: deferMessage(message); break; default: @@ -568,8 +501,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } } - class WaitForWifiDisableState extends State { - @Override + class P2pDisabledState extends State { + @Override public void enter() { if (DBG) logd(getName()); } @@ -578,34 +511,19 @@ public class WifiP2pService extends IWifiP2pManager.Stub { public boolean processMessage(Message message) { if (DBG) logd(getName() + message.toString()); switch (message.what) { - case WifiStateMachine.P2P_ENABLE_PROCEED: - try { - mNwService.wifiFirmwareReload(mInterface, "P2P"); - } catch (Exception e) { - loge("Failed to reload p2p firmware " + e); - // continue - } - - //A runtime crash can leave the interface up and - //this affects p2p when supplicant starts up. - //Ensure interface is down before a supplicant start. + case WifiStateMachine.CMD_ENABLE_P2P: try { - mNwService.setInterfaceDown(mInterface); - } catch (Exception e) { - if (DBG) Slog.w(TAG, "Unable to bring down wlan interface: " + e); - } - - if (WifiNative.startP2pSupplicant()) { - mWifiMonitor.startMonitoring(); - transitionTo(mP2pEnablingState); - } else { - notifyP2pEnableFailure(); - transitionTo(mP2pDisabledState); + mNwService.setInterfaceUp(mInterface); + } catch (RemoteException re) { + loge("Unable to change interface settings: " + re); + } catch (IllegalStateException ie) { + loge("Unable to change interface settings: " + ie); } + mWifiMonitor.startMonitoring(); + transitionTo(mP2pEnablingState); break; - case WifiP2pManager.ENABLE_P2P: - case WifiP2pManager.DISABLE_P2P: - deferMessage(message); + case WifiStateMachine.CMD_DISABLE_P2P: + //Nothing to do break; default: return NOT_HANDLED; @@ -625,22 +543,15 @@ public class WifiP2pService extends IWifiP2pManager.Stub { if (DBG) logd(getName() + message.toString()); switch (message.what) { case WifiMonitor.SUP_CONNECTION_EVENT: - logd("P2p start successful"); + if (DBG) logd("P2p socket connection successful"); transitionTo(mInactiveState); break; case WifiMonitor.SUP_DISCONNECTION_EVENT: - if (++mP2pRestartCount <= P2P_RESTART_TRIES) { - loge("Failed to start p2p, retry"); - WifiNative.killSupplicant(); - sendMessageDelayed(WifiP2pManager.ENABLE_P2P, P2P_RESTART_INTERVAL_MSECS); - } else { - loge("Failed " + mP2pRestartCount + " times to start p2p, quit "); - mP2pRestartCount = 0; - } + loge("P2p socket connection failed"); transitionTo(mP2pDisabledState); break; - case WifiP2pManager.ENABLE_P2P: - case WifiP2pManager.DISABLE_P2P: + case WifiStateMachine.CMD_ENABLE_P2P: + case WifiStateMachine.CMD_DISABLE_P2P: deferMessage(message); break; default: @@ -656,31 +567,58 @@ public class WifiP2pService extends IWifiP2pManager.Stub { if (DBG) logd(getName()); sendP2pStateChangedBroadcast(true); mNetworkInfo.setIsAvailable(true); + sendP2pConnectionChangedBroadcast(); initializeP2pSettings(); - showNotification(); } @Override public boolean processMessage(Message message) { if (DBG) logd(getName() + message.toString()); switch (message.what) { - case WifiP2pManager.ENABLE_P2P: - replyToMessage(message, WifiP2pManager.ENABLE_P2P_SUCCEEDED); + case WifiStateMachine.CMD_ENABLE_P2P: + //Nothing to do break; - case WifiP2pManager.DISABLE_P2P: + case WifiStateMachine.CMD_DISABLE_P2P: if (mPeers.clear()) sendP2pPeersChangedBroadcast(); - replyToMessage(message, WifiP2pManager.DISABLE_P2P_SUCCEEDED); + mWifiNative.closeSupplicantConnection(); transitionTo(mP2pDisablingState); break; case WifiP2pManager.DISCOVER_PEERS: - int timeout = message.arg1; - if (WifiNative.p2pFind(timeout)) { + // do not send service discovery request while normal find operation. + clearSupplicantServiceRequest(); + if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED); + sendP2pDiscoveryChangedBroadcast(true); } else { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, WifiP2pManager.ERROR); } break; + case WifiMonitor.P2P_FIND_STOPPED_EVENT: + sendP2pDiscoveryChangedBroadcast(false); + break; + case WifiP2pManager.STOP_DISCOVERY: + if (mWifiNative.p2pStopFind()) { + replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED, + WifiP2pManager.ERROR); + } + break; + case WifiP2pManager.DISCOVER_SERVICES: + if (DBG) logd(getName() + " discover services"); + if (!updateSupplicantServiceRequest()) { + replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED, + WifiP2pManager.NO_SERVICE_REQUESTS); + break; + } + if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) { + replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED, + WifiP2pManager.ERROR); + } + break; case WifiMonitor.P2P_DEVICE_FOUND_EVENT: WifiP2pDevice device = (WifiP2pDevice) message.obj; if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break; @@ -691,52 +629,53 @@ public class WifiP2pService extends IWifiP2pManager.Stub { device = (WifiP2pDevice) message.obj; if (mPeers.remove(device)) sendP2pPeersChangedBroadcast(); break; - case WifiP2pManager.CONNECT: - if (DBG) logd(getName() + " sending connect"); - mSavedConnectConfig = (WifiP2pConfig) message.obj; - mPersistGroup = false; - int netId = configuredNetworkId(mSavedConnectConfig.deviceAddress); - if (netId >= 0) { - //TODO: if failure, remove config and do a regular p2pConnect() - WifiNative.p2pReinvoke(netId, mSavedConnectConfig.deviceAddress); + case WifiP2pManager.ADD_LOCAL_SERVICE: + if (DBG) logd(getName() + " add service"); + WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj; + if (addLocalService(message.replyTo, servInfo)) { + replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED); } else { - boolean join = false; - if (isGroupOwner(mSavedConnectConfig.deviceAddress)) join = true; - String pin = WifiNative.p2pConnect(mSavedConnectConfig, join); - try { - Integer.parseInt(pin); - notifyWpsPin(pin, mSavedConnectConfig.deviceAddress); - } catch (NumberFormatException ignore) { - // do nothing if p2pConnect did not return a pin - } + replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED); } - updateDeviceStatus(mSavedConnectConfig.deviceAddress, WifiP2pDevice.INVITED); - sendP2pPeersChangedBroadcast(); - replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED); - transitionTo(mGroupNegotiationState); break; - case WifiMonitor.SUP_DISCONNECTION_EVENT: /* Supplicant died */ - loge("Connection lost, restart p2p"); - WifiNative.killSupplicant(); - WifiNative.closeSupplicantConnection(); - if (mPeers.clear()) sendP2pPeersChangedBroadcast(); - transitionTo(mP2pDisabledState); - sendMessageDelayed(WifiP2pManager.ENABLE_P2P, P2P_RESTART_INTERVAL_MSECS); - break; - case WifiMonitor.P2P_GROUP_STARTED_EVENT: - mGroup = (WifiP2pGroup) message.obj; - if (DBG) logd(getName() + " group started"); - if (mGroup.isGroupOwner()) { - startDhcpServer(mGroup.getInterface()); - } else { - mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext, - P2pStateMachine.this, mGroup.getInterface()); - mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); - WifiP2pDevice groupOwner = mGroup.getOwner(); - updateDeviceStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED); - sendP2pPeersChangedBroadcast(); + case WifiP2pManager.REMOVE_LOCAL_SERVICE: + if (DBG) logd(getName() + " remove service"); + servInfo = (WifiP2pServiceInfo)message.obj; + removeLocalService(message.replyTo, servInfo); + replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED); + break; + case WifiP2pManager.CLEAR_LOCAL_SERVICES: + if (DBG) logd(getName() + " clear service"); + clearLocalServices(message.replyTo); + break; + case WifiP2pManager.ADD_SERVICE_REQUEST: + if (DBG) logd(getName() + " add service request"); + if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) { + replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED); + break; + } + replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED); + break; + case WifiP2pManager.REMOVE_SERVICE_REQUEST: + if (DBG) logd(getName() + " remove service request"); + removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj); + replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED); + break; + case WifiP2pManager.CLEAR_SERVICE_REQUESTS: + if (DBG) logd(getName() + " clear service request"); + clearServiceRequests(message.replyTo); + replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED); + break; + case WifiMonitor.P2P_SERV_DISC_RESP_EVENT: + if (DBG) logd(getName() + " receive service response"); + List<WifiP2pServiceResponse> sdRespList = + (List<WifiP2pServiceResponse>) message.obj; + for (WifiP2pServiceResponse resp : sdRespList) { + WifiP2pDevice dev = + mPeers.get(resp.getSrcDevice().deviceAddress); + resp.setSrcDevice(dev); + sendServiceResponse(resp); } - transitionTo(mGroupCreatedState); break; default: return NOT_HANDLED; @@ -748,7 +687,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub { public void exit() { sendP2pStateChangedBroadcast(false); mNetworkInfo.setIsAvailable(false); - clearNotification(); } } @@ -757,21 +695,91 @@ public class WifiP2pService extends IWifiP2pManager.Stub { public void enter() { if (DBG) logd(getName()); //Start listening every time we get inactive - WifiNative.p2pListen(); + //TODO: Fix listen after driver behavior is fixed + //mWifiNative.p2pListen(); } @Override public boolean processMessage(Message message) { if (DBG) logd(getName() + message.toString()); switch (message.what) { + case WifiP2pManager.CONNECT: + if (DBG) logd(getName() + " sending connect"); + mSavedPeerConfig = (WifiP2pConfig) message.obj; + mPersistGroup = false; + int netId = configuredNetworkId(mSavedPeerConfig.deviceAddress); + if (netId >= 0) { + //TODO: if failure, remove config and do a regular p2pConnect() + mWifiNative.p2pReinvoke(netId, mSavedPeerConfig.deviceAddress); + } else { + //Stop discovery before issuing connect + mWifiNative.p2pStopFind(); + //If peer is a GO, we do not need to send provisional discovery, + //the supplicant takes care of it. + if (mWifiNative.isGroupOwner(mSavedPeerConfig.deviceAddress)) { + if (DBG) logd("Sending join to GO"); + p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); + transitionTo(mGroupNegotiationState); + } else { + if (DBG) logd("Sending prov disc"); + transitionTo(mProvisionDiscoveryState); + } + } + updateDeviceStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED); + sendP2pPeersChangedBroadcast(); + replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED); + break; case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT: - mSavedGoNegotiationConfig = (WifiP2pConfig) message.obj; - notifyP2pGoNegotationRequest(mSavedGoNegotiationConfig); - transitionTo(mUserAuthorizingGroupNegotiationState); + mSavedPeerConfig = (WifiP2pConfig) message.obj; + transitionTo(mUserAuthorizingInvitationState); + break; + case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT: + WifiP2pGroup group = (WifiP2pGroup) message.obj; + WifiP2pDevice owner = group.getOwner(); + + if (owner == null) { + if (DBG) loge("Ignored invitation from null owner"); + break; + } + + mSavedPeerConfig = new WifiP2pConfig(); + mSavedPeerConfig.deviceAddress = group.getOwner().deviceAddress; + + //Check if we have the owner in peer list and use appropriate + //wps method. Default is to use PBC. + if ((owner = getDeviceFromPeerList(owner.deviceAddress)) != null) { + if (owner.wpsPbcSupported()) { + mSavedPeerConfig.wps.setup = WpsInfo.PBC; + } else if (owner.wpsKeypadSupported()) { + mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD; + } else if (owner.wpsDisplaySupported()) { + mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY; + } + } + transitionTo(mUserAuthorizingInvitationState); + break; + case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT: + case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT: + case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT: + WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj; + mSavedPeerConfig = new WifiP2pConfig(); + mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress; + if (message.what == WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) { + mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD; + if (DBG) logd("Keypad prov disc request"); + } else if (message.what == WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) { + mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY; + mSavedPeerConfig.wps.pin = provDisc.pin; + if (DBG) logd("Display prov disc request"); + } else { + mSavedPeerConfig.wps.setup = WpsInfo.PBC; + if (DBG) logd("PBC prov disc request"); + } + transitionTo(mUserAuthorizingInvitationState); break; case WifiP2pManager.CREATE_GROUP: mPersistGroup = true; - if (WifiNative.p2pGroupAdd()) { + if (mWifiNative.p2pGroupAdd()) { replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED); } else { replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED, @@ -779,40 +787,47 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } transitionTo(mGroupNegotiationState); break; - case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT: - WifiP2pGroup group = (WifiP2pGroup) message.obj; - notifyP2pInvitationReceived(group); - transitionTo(mUserAuthorizingGroupInvitationState); - break; - default: + default: return NOT_HANDLED; } return HANDLED; } } - class UserAuthorizingGroupNegotiationState extends State { + class GroupCreatingState extends State { @Override public void enter() { if (DBG) logd(getName()); + sendMessageDelayed(obtainMessage(GROUP_CREATING_TIMED_OUT, + ++mGroupCreatingTimeoutIndex, 0), GROUP_CREATING_WAIT_TIME_MS); } @Override public boolean processMessage(Message message) { if (DBG) logd(getName() + message.toString()); switch (message.what) { - case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT: - case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT: - //Ignore additional connection requests + case GROUP_CREATING_TIMED_OUT: + if (mGroupCreatingTimeoutIndex == message.arg1) { + if (DBG) logd("Group negotiation timed out"); + handleGroupCreationFailure(); + transitionTo(mInactiveState); + } break; - case GROUP_NEGOTIATION_USER_ACCEPT: - sendMessage(WifiP2pManager.CONNECT, mSavedGoNegotiationConfig); - mSavedGoNegotiationConfig = null; + case WifiP2pManager.DISCOVER_PEERS: + /* Discovery will break negotiation */ + replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, + WifiP2pManager.BUSY); break; - case GROUP_NEGOTIATION_USER_REJECT: - if (DBG) logd("User rejected incoming negotiation request"); - mSavedGoNegotiationConfig = null; + case WifiP2pManager.CANCEL_CONNECT: + //Do a supplicant p2p_cancel which only cancels an ongoing + //group negotiation. This will fail for a pending provision + //discovery or for a pending user action, but at the framework + //level, we always treat cancel as succeded and enter + //an inactive state + mWifiNative.p2pCancelConnect(); + handleGroupCreationFailure(); transitionTo(mInactiveState); + replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED); break; default: return NOT_HANDLED; @@ -821,30 +836,31 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } } - class UserAuthorizingGroupInvitationState extends State { + class UserAuthorizingInvitationState extends State { @Override public void enter() { if (DBG) logd(getName()); + notifyInvitationReceived(); } @Override public boolean processMessage(Message message) { if (DBG) logd(getName() + message.toString()); switch (message.what) { - case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT: - case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT: - //Ignore additional connection requests - break; - case GROUP_INVITATION_USER_ACCEPT: - if (DBG) logd(getName() + " connect to invited group"); - WifiP2pConfig config = new WifiP2pConfig(); - config.deviceAddress = mSavedP2pGroup.getOwner().deviceAddress; - sendMessage(WifiP2pManager.CONNECT, config); - mSavedP2pGroup = null; - break; - case GROUP_INVITATION_USER_REJECT: - if (DBG) logd("User rejected incoming invitation request"); - mSavedP2pGroup = null; + case PEER_CONNECTION_USER_ACCEPT: + //TODO: handle persistence + if (mWifiNative.isGroupOwner(mSavedPeerConfig.deviceAddress)) { + p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); + } else { + p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP); + } + updateDeviceStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED); + sendP2pPeersChangedBroadcast(); + transitionTo(mGroupNegotiationState); + break; + case PEER_CONNECTION_USER_REJECT: + if (DBG) logd("User rejected invitation " + mSavedPeerConfig); + mSavedPeerConfig = null; transitionTo(mInactiveState); break; default: @@ -852,15 +868,77 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } return HANDLED; } + + @Override + public void exit() { + //TODO: dismiss dialog if not already done + } } + class ProvisionDiscoveryState extends State { + @Override + public void enter() { + if (DBG) logd(getName()); + mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) logd(getName() + message.toString()); + WifiP2pProvDiscEvent provDisc; + WifiP2pDevice device; + switch (message.what) { + case WifiMonitor.P2P_PROV_DISC_PBC_RSP_EVENT: + provDisc = (WifiP2pProvDiscEvent) message.obj; + device = provDisc.device; + if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break; + + if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) { + if (DBG) logd("Found a match " + mSavedPeerConfig); + mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP); + transitionTo(mGroupNegotiationState); + } + break; + case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT: + provDisc = (WifiP2pProvDiscEvent) message.obj; + device = provDisc.device; + if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break; + + if (mSavedPeerConfig.wps.setup == WpsInfo.KEYPAD) { + if (DBG) logd("Found a match " + mSavedPeerConfig); + /* we already have the pin */ + if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) { + mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP); + transitionTo(mGroupNegotiationState); + } else { + transitionTo(mUserAuthorizingInvitationState); + } + } + break; + case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT: + provDisc = (WifiP2pProvDiscEvent) message.obj; + device = provDisc.device; + if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break; + + if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) { + if (DBG) logd("Found a match " + mSavedPeerConfig); + mSavedPeerConfig.wps.pin = provDisc.pin; + mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP); + notifyInvitationSent(provDisc.pin, device.deviceAddress); + transitionTo(mGroupNegotiationState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } class GroupNegotiationState extends State { @Override public void enter() { if (DBG) logd(getName()); - sendMessageDelayed(obtainMessage(GROUP_NEGOTIATION_TIMED_OUT, - ++mGroupNegotiationTimeoutIndex, 0), GROUP_NEGOTIATION_WAIT_TIME_MS); } @Override @@ -873,36 +951,28 @@ public class WifiP2pService extends IWifiP2pManager.Stub { case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT: if (DBG) logd(getName() + " go success"); break; + case WifiMonitor.P2P_GROUP_STARTED_EVENT: + mGroup = (WifiP2pGroup) message.obj; + if (DBG) logd(getName() + " group started"); + if (mGroup.isGroupOwner()) { + startDhcpServer(mGroup.getInterface()); + } else { + mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext, + P2pStateMachine.this, mGroup.getInterface()); + mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); + WifiP2pDevice groupOwner = mGroup.getOwner(); + updateDeviceStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED); + sendP2pPeersChangedBroadcast(); + } + mSavedPeerConfig = null; + transitionTo(mGroupCreatedState); + break; case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT: case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT: if (DBG) logd(getName() + " go failure"); - updateDeviceStatus(mSavedConnectConfig.deviceAddress, WifiP2pDevice.FAILED); - mSavedConnectConfig = null; - sendP2pPeersChangedBroadcast(); + handleGroupCreationFailure(); transitionTo(mInactiveState); break; - case GROUP_NEGOTIATION_TIMED_OUT: - if (mGroupNegotiationTimeoutIndex == message.arg1) { - if (DBG) logd("Group negotiation timed out"); - updateDeviceStatus(mSavedConnectConfig.deviceAddress, WifiP2pDevice.FAILED); - mSavedConnectConfig = null; - sendP2pPeersChangedBroadcast(); - transitionTo(mInactiveState); - } - break; - case WifiP2pManager.DISCOVER_PEERS: - /* Discovery will break negotiation */ - replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, - WifiP2pManager.BUSY); - break; - case WifiP2pManager.CANCEL_CONNECT: - if (WifiNative.p2pCancelConnect()) { - replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED); - } else { - replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED, - WifiP2pManager.ERROR); - } - break; default: return NOT_HANDLED; } @@ -910,6 +980,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } } + + class GroupCreatedState extends State { @Override public void enter() { @@ -923,6 +995,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub { setWifiP2pInfoOnGroupFormation(SERVER_ADDRESS); sendP2pConnectionChangedBroadcast(); } + + if (!mPersistGroup) { + mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S); + } } @Override @@ -930,28 +1006,27 @@ public class WifiP2pService extends IWifiP2pManager.Stub { if (DBG) logd(getName() + message.toString()); switch (message.what) { case WifiMonitor.AP_STA_CONNECTED_EVENT: - //After a GO setup, STA connected event comes with interface address - String interfaceAddress = (String) message.obj; - String deviceAddress = getDeviceAddress(interfaceAddress); + WifiP2pDevice device = (WifiP2pDevice) message.obj; + String deviceAddress = device.deviceAddress; if (deviceAddress != null) { mGroup.addClient(deviceAddress); updateDeviceStatus(deviceAddress, WifiP2pDevice.CONNECTED); if (DBG) logd(getName() + " ap sta connected"); sendP2pPeersChangedBroadcast(); } else { - loge("Connect on unknown device address : " + interfaceAddress); + loge("Connect on null device address, ignore"); } break; case WifiMonitor.AP_STA_DISCONNECTED_EVENT: - interfaceAddress = (String) message.obj; - deviceAddress = getDeviceAddress(interfaceAddress); + device = (WifiP2pDevice) message.obj; + deviceAddress = device.deviceAddress; if (deviceAddress != null) { updateDeviceStatus(deviceAddress, WifiP2pDevice.AVAILABLE); if (mGroup.removeClient(deviceAddress)) { if (DBG) logd("Removed client " + deviceAddress); if (!mPersistGroup && mGroup.isClientListEmpty()) { Slog.d(TAG, "Client list empty, remove non-persistent p2p group"); - WifiNative.p2pGroupRemove(mGroup.getInterface()); + mWifiNative.p2pGroupRemove(mGroup.getInterface()); } } else { if (DBG) logd("Failed to remove client " + deviceAddress); @@ -962,7 +1037,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { sendP2pPeersChangedBroadcast(); if (DBG) loge(getName() + " ap sta disconnected"); } else { - loge("Disconnect on unknown device address : " + interfaceAddress); + loge("Disconnect on unknown device: " + device); } break; case DhcpStateMachine.CMD_POST_DHCP_ACTION: @@ -972,13 +1047,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub { if (DBG) logd("DhcpInfo: " + dhcpInfo); setWifiP2pInfoOnGroupFormation(dhcpInfo.serverAddress); sendP2pConnectionChangedBroadcast(); + //Turn on power save on client + mWifiNative.setP2pPowerSave(mGroup.getInterface(), true); } else { - WifiNative.p2pGroupRemove(mGroup.getInterface()); + loge("DHCP failed"); + mWifiNative.p2pGroupRemove(mGroup.getInterface()); } break; case WifiP2pManager.REMOVE_GROUP: if (DBG) loge(getName() + " remove group"); - if (WifiNative.p2pGroupRemove(mGroup.getInterface())) { + if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) { replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED); } else { replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED, @@ -1006,29 +1084,27 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } mGroup = null; + mWifiNative.p2pFlush(); if (changed) sendP2pPeersChangedBroadcast(); transitionTo(mInactiveState); break; case WifiMonitor.P2P_DEVICE_LOST_EVENT: - WifiP2pDevice device = (WifiP2pDevice) message.obj; - if (device.equals(mGroup.getOwner())) { - logd("Lost the group owner, killing p2p connection"); - WifiNative.p2pGroupRemove(mGroup.getInterface()); - } else if (mGroup.removeClient(device)) { - if (!mPersistGroup && mGroup.isClientListEmpty()) { - Slog.d(TAG, "Client list empty, removing a non-persistent p2p group"); - WifiNative.p2pGroupRemove(mGroup.getInterface()); - } + device = (WifiP2pDevice) message.obj; + //Device loss for a connected device indicates it is not in discovery any more + if (mGroup.contains(device)) { + if (DBG) logd("Lost " + device +" , do nothing"); + return HANDLED; } - return NOT_HANDLED; // Do the regular device lost handling - case WifiP2pManager.DISABLE_P2P: + // Do the regular device lost handling + return NOT_HANDLED; + case WifiStateMachine.CMD_DISABLE_P2P: sendMessage(WifiP2pManager.REMOVE_GROUP); deferMessage(message); break; case WifiP2pManager.CONNECT: WifiP2pConfig config = (WifiP2pConfig) message.obj; logd("Inviting device : " + config.deviceAddress); - if (WifiNative.p2pInvite(mGroup, config.deviceAddress)) { + if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) { updateDeviceStatus(config.deviceAddress, WifiP2pDevice.INVITED); sendP2pPeersChangedBroadcast(); replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED); @@ -1038,24 +1114,25 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } // TODO: figure out updating the status to declined when invitation is rejected break; - case WifiMonitor.P2P_INVITATION_RESULT_EVENT: - logd("===> INVITATION RESULT EVENT : " + message.obj); - break; case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT: - notifyP2pProvDiscPbcRequest((WifiP2pDevice) message.obj); - break; case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT: - notifyP2pProvDiscPinRequest((WifiP2pDevice) message.obj); + case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT: + WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj; + mSavedPeerConfig = new WifiP2pConfig(); + mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress; + if (message.what == WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) { + mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD; + } else if (message.what == WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) { + mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY; + mSavedPeerConfig.wps.pin = provDisc.pin; + } else { + mSavedPeerConfig.wps.setup = WpsInfo.PBC; + } + transitionTo(mUserAuthorizingJoinState); break; case WifiMonitor.P2P_GROUP_STARTED_EVENT: Slog.e(TAG, "Duplicate group creation event notice, ignore"); break; - case WPS_PBC: - WifiNative.wpsPbc(); - break; - case WPS_PIN: - WifiNative.wpsPin((String) message.obj); - break; default: return NOT_HANDLED; } @@ -1070,6 +1147,48 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } } + class UserAuthorizingJoinState extends State { + @Override + public void enter() { + if (DBG) logd(getName()); + notifyInvitationReceived(); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) logd(getName() + message.toString()); + switch (message.what) { + case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT: + case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT: + case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT: + //Ignore more client requests + break; + case PEER_CONNECTION_USER_ACCEPT: + if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) { + mWifiNative.startWpsPbc(null); + } else { + mWifiNative.startWpsPinKeypad(mSavedPeerConfig.wps.pin); + } + mSavedPeerConfig = null; + transitionTo(mGroupCreatedState); + break; + case PEER_CONNECTION_USER_REJECT: + if (DBG) logd("User rejected incoming request"); + mSavedPeerConfig = null; + transitionTo(mGroupCreatedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + @Override + public void exit() { + //TODO: dismiss dialog if not already done + } + } + private void sendP2pStateChangedBroadcast(boolean enabled) { final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); @@ -1083,6 +1202,20 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mContext.sendStickyBroadcast(intent); } + private void sendP2pDiscoveryChangedBroadcast(boolean started) { + if (mDiscoveryStarted == started) return; + mDiscoveryStarted = started; + + if (DBG) logd("discovery change broadcast " + started); + + final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, started ? + WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED : + WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED); + mContext.sendStickyBroadcast(intent); + } + private void sendThisDeviceChangedBroadcast() { final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); @@ -1110,9 +1243,9 @@ public class WifiP2pService extends IWifiP2pManager.Stub { InterfaceConfiguration ifcg = null; try { ifcg = mNwService.getInterfaceConfig(intf); - ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress( - SERVER_ADDRESS), 24); - ifcg.interfaceFlags = "[up]"; + ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress( + SERVER_ADDRESS), 24)); + ifcg.setInterfaceUp(); mNwService.setInterfaceConfig(intf, ifcg); /* This starts the dnsmasq server */ mNwService.startTethering(DHCP_RANGE); @@ -1146,140 +1279,80 @@ public class WifiP2pService extends IWifiP2pManager.Stub { dialog.show(); } - private void notifyWpsPin(String pin, String peerAddress) { + private void addRowToDialog(ViewGroup group, int stringId, String value) { Resources r = Resources.getSystem(); - AlertDialog dialog = new AlertDialog.Builder(mContext) - .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) - .setMessage(r.getString(R.string.wifi_p2p_pin_display_message, pin, peerAddress)) - .setPositiveButton(r.getString(R.string.ok), null) - .create(); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - dialog.show(); + View row = LayoutInflater.from(mContext).inflate(R.layout.wifi_p2p_dialog_row, + group, false); + ((TextView) row.findViewById(R.id.name)).setText(r.getString(stringId)); + ((TextView) row.findViewById(R.id.value)).setText(value); + group.addView(row); } - private void notifyP2pGoNegotationRequest(WifiP2pConfig config) { + private void notifyInvitationSent(String pin, String peerAddress) { Resources r = Resources.getSystem(); - WpsInfo wps = config.wps; - final View textEntryView = LayoutInflater.from(mContext) - .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); - final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); - AlertDialog dialog = new AlertDialog.Builder(mContext) - .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) - .setView(textEntryView) - .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (DBG) logd(getName() + " connect " + pin.getText()); - - if (pin.getVisibility() == View.GONE) { - mSavedGoNegotiationConfig.wps.setup = WpsInfo.PBC; - } else { - mSavedGoNegotiationConfig.wps.setup = WpsInfo.KEYPAD; - mSavedGoNegotiationConfig.wps.pin = pin.getText().toString(); - } - sendMessage(GROUP_NEGOTIATION_USER_ACCEPT); - } - }) - .setNegativeButton(r.getString(R.string.cancel), new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (DBG) logd(getName() + " ignore connect"); - sendMessage(GROUP_NEGOTIATION_USER_REJECT); - } - }) - .create(); - - if (wps.setup == WpsInfo.PBC) { - pin.setVisibility(View.GONE); - dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message, - config.deviceAddress)); - } else { - dialog.setMessage(r.getString(R.string.wifi_p2p_pin_go_negotiation_request_message, - config.deviceAddress)); - } - - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - dialog.show(); - } - - private void notifyP2pProvDiscPbcRequest(WifiP2pDevice peer) { - Resources r = Resources.getSystem(); final View textEntryView = LayoutInflater.from(mContext) - .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); - final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); + .inflate(R.layout.wifi_p2p_dialog, null); + + ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info); + addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress)); + addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin); AlertDialog dialog = new AlertDialog.Builder(mContext) - .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title)) .setView(textEntryView) - .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (DBG) logd(getName() + " wps_pbc"); - sendMessage(WPS_PBC); - } - }) - .setNegativeButton(r.getString(R.string.cancel), null) + .setPositiveButton(r.getString(R.string.ok), null) .create(); - - pin.setVisibility(View.GONE); - dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message, - peer.deviceAddress)); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.show(); } - private void notifyP2pProvDiscPinRequest(WifiP2pDevice peer) { + private void notifyInvitationReceived() { Resources r = Resources.getSystem(); + final WpsInfo wps = mSavedPeerConfig.wps; final View textEntryView = LayoutInflater.from(mContext) - .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); - final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); - - AlertDialog dialog = new AlertDialog.Builder(mContext) - .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) - .setView(textEntryView) - .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (DBG) logd(getName() + " wps_pin"); - sendMessage(WPS_PIN, pin.getText().toString()); - } - }) - .setNegativeButton(r.getString(R.string.cancel), null) - .create(); + .inflate(R.layout.wifi_p2p_dialog, null); - dialog.setMessage(r.getString(R.string.wifi_p2p_pin_go_negotiation_request_message, - peer.deviceAddress)); + ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info); + addRowToDialog(group, R.string.wifi_p2p_from_message, getDeviceName( + mSavedPeerConfig.deviceAddress)); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - dialog.show(); - } - - private void notifyP2pInvitationReceived(WifiP2pGroup group) { - mSavedP2pGroup = group; - Resources r = Resources.getSystem(); - final View textEntryView = LayoutInflater.from(mContext) - .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); - final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); + final EditText pin = (EditText) textEntryView.findViewById(R.id.wifi_p2p_wps_pin); AlertDialog dialog = new AlertDialog.Builder(mContext) - .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setTitle(r.getString(R.string.wifi_p2p_invitation_to_connect_title)) .setView(textEntryView) - .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { + .setPositiveButton(r.getString(R.string.accept), new OnClickListener() { public void onClick(DialogInterface dialog, int which) { - sendMessage(GROUP_INVITATION_USER_ACCEPT); + if (wps.setup == WpsInfo.KEYPAD) { + mSavedPeerConfig.wps.pin = pin.getText().toString(); + } + if (DBG) logd(getName() + " accept invitation " + mSavedPeerConfig); + sendMessage(PEER_CONNECTION_USER_ACCEPT); } }) - .setNegativeButton(r.getString(R.string.cancel), new OnClickListener() { + .setNegativeButton(r.getString(R.string.decline), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - if (DBG) logd(getName() + " ignore invite"); - sendMessage(GROUP_INVITATION_USER_REJECT); + if (DBG) logd(getName() + " ignore connect"); + sendMessage(PEER_CONNECTION_USER_REJECT); } }) .create(); - pin.setVisibility(View.GONE); - dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message, - group.getOwner().deviceAddress)); + //make the enter pin area or the display pin area visible + switch (wps.setup) { + case WpsInfo.KEYPAD: + if (DBG) logd("Enter pin section visible"); + textEntryView.findViewById(R.id.enter_pin_section).setVisibility(View.VISIBLE); + break; + case WpsInfo.DISPLAY: + if (DBG) logd("Shown pin section visible"); + addRowToDialog(group, R.string.wifi_p2p_show_pin_message, wps.pin); + break; + default: + break; + } dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.show(); @@ -1293,15 +1366,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub { } } - private boolean isGroupOwner(String deviceAddress) { - for (WifiP2pDevice d : mPeers.getDeviceList()) { - if (d.deviceAddress.equals(deviceAddress)) { - return d.isGroupOwner(); - } - } - return false; - } - //TODO: implement when wpa_supplicant is fixed private int configuredNetworkId(String deviceAddress) { return -1; @@ -1319,23 +1383,56 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mWifiP2pInfo.groupOwnerAddress = null; } - private String getDeviceAddress(String interfaceAddress) { + private String getDeviceName(String deviceAddress) { for (WifiP2pDevice d : mPeers.getDeviceList()) { - if (interfaceAddress.equals(WifiNative.p2pGetInterfaceAddress(d.deviceAddress))) { - return d.deviceAddress; + if (d.deviceAddress.equals(deviceAddress)) { + return d.deviceName; + } + } + //Treat the address as name if there is no match + return deviceAddress; + } + + private WifiP2pDevice getDeviceFromPeerList(String deviceAddress) { + for (WifiP2pDevice d : mPeers.getDeviceList()) { + if (d.deviceAddress.equals(deviceAddress)) { + return d; } } return null; } - private void initializeP2pSettings() { - WifiNative.setPersistentReconnect(true); - WifiNative.setDeviceName(mThisDevice.deviceName); - WifiNative.setDeviceType(mThisDevice.primaryDeviceType); + private void p2pConnectWithPinDisplay(WifiP2pConfig config, boolean join) { + String pin = mWifiNative.p2pConnect(config, join); + try { + Integer.parseInt(pin); + notifyInvitationSent(pin, config.deviceAddress); + } catch (NumberFormatException ignore) { + // do nothing if p2pConnect did not return a pin + } + } - mThisDevice.deviceAddress = WifiNative.p2pGetDeviceAddress(); + private void initializeP2pSettings() { + mWifiNative.setPersistentReconnect(true); + mWifiNative.setDeviceName(mThisDevice.deviceName); + //DIRECT-XY-DEVICENAME (XY is randomly generated) + mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName); + mWifiNative.setDeviceType(mThisDevice.primaryDeviceType); + //The supplicant default is to support everything, but a bug necessitates + //the framework to specify this explicitly + mWifiNative.setConfigMethods("keypad display push_button"); + //STA has higher priority over P2P + mWifiNative.setConcurrencyPriority("sta"); + + mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress(); updateThisDevice(WifiP2pDevice.AVAILABLE); if (DBG) Slog.d(TAG, "DeviceAddress: " + mThisDevice.deviceAddress); + + mClientInfoList.clear(); + mWifiNative.p2pFlush(); + mWifiNative.p2pServiceFlush(); + mServiceTransactionId = 0; + mServiceDiscReqId = null; } private void updateThisDevice(int status) { @@ -1343,6 +1440,14 @@ public class WifiP2pService extends IWifiP2pManager.Stub { sendThisDeviceChangedBroadcast(); } + private void handleGroupCreationFailure() { + mSavedPeerConfig = null; + /* After cancelling group formation, new connections on existing peers can fail + * at supplicant. Flush and restart discovery */ + mWifiNative.p2pFlush(); + sendMessage(WifiP2pManager.DISCOVER_PEERS); + } + //State machine initiated requests can have replyTo set to null indicating //there are no recepients, we ignore those reply actions private void replyToMessage(Message msg, int what) { @@ -1384,54 +1489,281 @@ public class WifiP2pService extends IWifiP2pManager.Stub { Slog.e(TAG, s); } - private void showNotification() { - NotificationManager notificationManager = - (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager == null || mNotification != null) { + /** + * Update service discovery request to wpa_supplicant. + */ + private boolean updateSupplicantServiceRequest() { + clearSupplicantServiceRequest(); + + StringBuffer sb = new StringBuffer(); + for (ClientInfo c: mClientInfoList.values()) { + int key; + WifiP2pServiceRequest req; + for (int i=0; i < c.mReqList.size(); i++) { + req = c.mReqList.valueAt(i); + if (req != null) { + sb.append(req.getSupplicantQuery()); + } + } + } + + if (sb.length() == 0) { + return false; + } + + mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString()); + if (mServiceDiscReqId == null) { + return false; + } + return true; + } + + /** + * Clear service discovery request in wpa_supplicant + */ + private void clearSupplicantServiceRequest() { + if (mServiceDiscReqId == null) return; + + mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId); + mServiceDiscReqId = null; + } + + /* TODO: We could track individual service adds seperately and avoid + * having to do update all service requests on every new request + */ + private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) { + clearClientDeadChannels(); + ClientInfo clientInfo = getClientInfo(m, true); + if (clientInfo == null) { + return false; + } + + ++mServiceTransactionId; + //The Wi-Fi p2p spec says transaction id should be non-zero + if (mServiceTransactionId == 0) ++mServiceTransactionId; + req.setTransactionId(mServiceTransactionId); + clientInfo.mReqList.put(mServiceTransactionId, req); + + if (mServiceDiscReqId == null) { + return true; + } + + return updateSupplicantServiceRequest(); + } + + private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) { + ClientInfo clientInfo = getClientInfo(m, false); + if (clientInfo == null) { return; } - Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + //Application does not have transaction id information + //go through stored requests to remove + boolean removed = false; + for (int i=0; i < clientInfo.mReqList.size(); i++) { + if (req.equals(clientInfo.mReqList.valueAt(i))) { + removed = true; + clientInfo.mReqList.removeAt(i); + break; + } + } - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + if (!removed) return; - Resources r = Resources.getSystem(); - CharSequence title = r.getText(R.string.wifi_p2p_enabled_notification_title); - CharSequence message = r.getText(R.string.wifi_p2p_enabled_notification_message); - - mNotification = new Notification(); - mNotification.when = 0; - //TODO: might change to be a seperate icon - mNotification.icon = R.drawable.stat_sys_tether_wifi; - mNotification.defaults &= ~Notification.DEFAULT_SOUND; - mNotification.flags = Notification.FLAG_ONGOING_EVENT; - mNotification.tickerText = title; - mNotification.setLatestEventInfo(mContext, title, message, pi); - - notificationManager.notify(mNotification.icon, mNotification); + if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) { + if (DBG) logd("remove client information from framework"); + mClientInfoList.remove(clientInfo.mMessenger); + } + + if (mServiceDiscReqId == null) { + return; + } + + updateSupplicantServiceRequest(); } - private void clearNotification() { - NotificationManager notificationManager = - (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager != null && mNotification != null) { - notificationManager.cancel(mNotification.icon); - mNotification = null; + private void clearServiceRequests(Messenger m) { + + ClientInfo clientInfo = getClientInfo(m, false); + if (clientInfo == null) { + return; + } + + if (clientInfo.mReqList.size() == 0) { + return; + } + + clientInfo.mReqList.clear(); + + if (clientInfo.mServList.size() == 0) { + if (DBG) logd("remove channel information from framework"); + mClientInfoList.remove(clientInfo.mMessenger); + } + + if (mServiceDiscReqId == null) { + return; } + + updateSupplicantServiceRequest(); } - private boolean isAirplaneSensitive() { - String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_RADIOS); - return airplaneModeRadios == null - || airplaneModeRadios.contains(Settings.System.RADIO_WIFI); + private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) { + clearClientDeadChannels(); + ClientInfo clientInfo = getClientInfo(m, true); + if (clientInfo == null) { + return false; + } + + if (!clientInfo.mServList.add(servInfo)) { + return false; + } + + if (!mWifiNative.p2pServiceAdd(servInfo)) { + clientInfo.mServList.remove(servInfo); + return false; + } + + return true; } - private boolean isAirplaneModeOn() { - return isAirplaneSensitive() && Settings.System.getInt(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_ON, 0) == 1; + private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) { + ClientInfo clientInfo = getClientInfo(m, false); + if (clientInfo == null) { + return; + } + + mWifiNative.p2pServiceDel(servInfo); + + clientInfo.mServList.remove(servInfo); + if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) { + if (DBG) logd("remove client information from framework"); + mClientInfoList.remove(clientInfo.mMessenger); + } } + private void clearLocalServices(Messenger m) { + ClientInfo clientInfo = getClientInfo(m, false); + if (clientInfo == null) { + return; + } + + for (WifiP2pServiceInfo servInfo: clientInfo.mServList) { + mWifiNative.p2pServiceDel(servInfo); + } + + clientInfo.mServList.clear(); + if (clientInfo.mReqList.size() == 0) { + if (DBG) logd("remove client information from framework"); + mClientInfoList.remove(clientInfo.mMessenger); + } + } + + private void clearClientInfo(Messenger m) { + clearLocalServices(m); + clearServiceRequests(m); + } + + /** + * Send the service response to the WifiP2pManager.Channel. + * + * @param resp + */ + private void sendServiceResponse(WifiP2pServiceResponse resp) { + for (ClientInfo c : mClientInfoList.values()) { + WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId()); + if (req != null) { + Message msg = Message.obtain(); + msg.what = WifiP2pManager.RESPONSE_SERVICE; + msg.arg1 = 0; + msg.arg2 = 0; + msg.obj = resp; + try { + c.mMessenger.send(msg); + } catch (RemoteException e) { + if (DBG) logd("detect dead channel"); + clearClientInfo(c.mMessenger); + return; + } + } + } + } + + /** + * We dont get notifications of clients that have gone away. + * We detect this actively when services are added and throw + * them away. + * + * TODO: This can be done better with full async channels. + */ + private void clearClientDeadChannels() { + ArrayList<Messenger> deadClients = new ArrayList<Messenger>(); + + for (ClientInfo c : mClientInfoList.values()) { + Message msg = Message.obtain(); + msg.what = WifiP2pManager.PING; + msg.arg1 = 0; + msg.arg2 = 0; + msg.obj = null; + try { + c.mMessenger.send(msg); + } catch (RemoteException e) { + if (DBG) logd("detect dead channel"); + deadClients.add(c.mMessenger); + } + } + + for (Messenger m : deadClients) { + clearClientInfo(m); + } + } + + /** + * Return the specified ClientInfo. + * @param m Messenger + * @param createIfNotExist if true and the specified channel info does not exist, + * create new client info. + * @return the specified ClientInfo. + */ + private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) { + ClientInfo clientInfo = mClientInfoList.get(m); + + if (clientInfo == null && createIfNotExist) { + if (DBG) logd("add a new client"); + clientInfo = new ClientInfo(m); + mClientInfoList.put(m, clientInfo); + } + + return clientInfo; + } + + } + + /** + * Information about a particular client and we track the service discovery requests + * and the local services registered by the client. + */ + private class ClientInfo { + + /* + * A reference to WifiP2pManager.Channel handler. + * The response of this request is notified to WifiP2pManager.Channel handler + */ + private Messenger mMessenger; + + /* + * A service discovery request list. + */ + private SparseArray<WifiP2pServiceRequest> mReqList; + + /* + * A local service information list. + */ + private List<WifiP2pServiceInfo> mServList; + + private ClientInfo(Messenger m) { + mMessenger = m; + mReqList = new SparseArray(); + mServList = new ArrayList<WifiP2pServiceInfo>(); + } } } diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java new file mode 100644 index 0000000..54b7ac4 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.net.nsd.DnsSdTxtRecord; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A class for storing Bonjour service information that is advertised + * over a Wi-Fi peer-to-peer setup. + * + * {@see android.net.wifi.p2p.WifiP2pManager#addLocalService} + * {@see android.net.wifi.p2p.WifiP2pManager#removeLocalService} + * {@see WifiP2pServiceInfo} + * {@see WifiP2pUpnpServiceInfo} + */ +public class WifiP2pDnsSdServiceInfo extends WifiP2pServiceInfo { + + /** + * Bonjour version 1. + * @hide + */ + public static final int VERSION_1 = 0x01; + + /** + * Pointer record. + * @hide + */ + public static final int DNS_TYPE_PTR = 12; + + /** + * Text record. + * @hide + */ + public static final int DNS_TYPE_TXT = 16; + + /** + * virtual memory packet. + * see E.3 of the Wi-Fi Direct technical specification for the detail.<br> + * Key: domain name Value: pointer address.<br> + */ + private final static Map<String, String> sVmPacket; + + static { + sVmPacket = new HashMap<String, String>(); + sVmPacket.put("_tcp.local.", "c00c"); + sVmPacket.put("local.", "c011"); + sVmPacket.put("_udp.local.", "c01c"); + } + + /** + * This constructor is only used in newInstance(). + * + * @param queryList + */ + private WifiP2pDnsSdServiceInfo(List<String> queryList) { + super(queryList); + } + + /** + * Create a Bonjour service information object. + * + * @param instanceName instance name.<br> + * e.g) "MyPrinter" + * @param serviceType service type.<br> + * e.g) "_ipp._tcp" + * @param txtRecord TXT record as defined at + * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt + * @return Bonjour service information object + */ + public static WifiP2pDnsSdServiceInfo newInstance(String instanceName, + String serviceType, DnsSdTxtRecord txtRecord) { + if (TextUtils.isEmpty(instanceName) || TextUtils.isEmpty(serviceType)) { + throw new IllegalArgumentException( + "instance name or service type cannot be empty"); + } + + if (txtRecord == null) { + txtRecord = new DnsSdTxtRecord(); + } + + ArrayList<String> queries = new ArrayList<String>(); + queries.add(createPtrServiceQuery(instanceName, serviceType)); + queries.add(createTxtServiceQuery(instanceName, serviceType, txtRecord)); + + return new WifiP2pDnsSdServiceInfo(queries); + } + + /** + * Create wpa_supplicant service query for PTR record. + * + * @param instanceName instance name.<br> + * e.g) "MyPrinter" + * @param serviceType service type.<br> + * e.g) "_ipp._tcp" + * @return wpa_supplicant service query. + */ + private static String createPtrServiceQuery(String instanceName, + String serviceType) { + + StringBuffer sb = new StringBuffer(); + sb.append("bonjour "); + sb.append(createRequest(serviceType + ".local.", DNS_TYPE_PTR, VERSION_1)); + sb.append(" "); + + byte[] data = instanceName.getBytes(); + sb.append(String.format("%02x", data.length)); + sb.append(WifiP2pServiceInfo.bin2HexStr(data)); + // This is the start point of this response. + // Therefore, it indicates the request domain name. + sb.append("c027"); + return sb.toString(); + } + + /** + * Create wpa_supplicant service query for TXT record. + * + * @param instanceName instance name.<br> + * e.g) "MyPrinter" + * @param serviceType service type.<br> + * e.g) "_ipp._tcp" + * @param txtRecord TXT record.<br> + * @return wpa_supplicant service query. + */ + private static String createTxtServiceQuery(String instanceName, + String serviceType, + DnsSdTxtRecord txtRecord) { + + + StringBuffer sb = new StringBuffer(); + sb.append("bonjour "); + + sb.append(createRequest((instanceName + "." + serviceType + ".local."), + DNS_TYPE_TXT, VERSION_1)); + sb.append(" "); + byte[] rawData = txtRecord.getRawData(); + if (rawData.length == 0) { + sb.append("00"); + } else { + sb.append(bin2HexStr(rawData)); + } + return sb.toString(); + } + + /** + * Create bonjour service discovery request. + * + * @param dnsName dns name + * @param dnsType dns type + * @param version version number + * @hide + */ + static String createRequest(String dnsName, int dnsType, int version) { + StringBuffer sb = new StringBuffer(); + + /* + * The request format is as follows. + * ________________________________________________ + * | Encoded and Compressed dns name (variable) | + * ________________________________________________ + * | Type (2) | Version (1) | + */ + if (dnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) { + dnsName = dnsName.toLowerCase(); + } + sb.append(compressDnsName(dnsName)); + sb.append(String.format("%04x", dnsType)); + sb.append(String.format("%02x", version)); + + return sb.toString(); + } + + /** + * Compress DNS data. + * + * see E.3 of the Wi-Fi Direct technical specification for the detail. + * + * @param dnsName dns name + * @return compressed dns name + */ + private static String compressDnsName(String dnsName) { + StringBuffer sb = new StringBuffer(); + + // The domain name is replaced with a pointer to a prior + // occurrence of the same name in virtual memory packet. + while (true) { + String data = sVmPacket.get(dnsName); + if (data != null) { + sb.append(data); + break; + } + int i = dnsName.indexOf('.'); + if (i == -1) { + if (dnsName.length() > 0) { + sb.append(String.format("%02x", dnsName.length())); + sb.append(WifiP2pServiceInfo.bin2HexStr(dnsName.getBytes())); + } + // for a sequence of labels ending in a zero octet + sb.append("00"); + break; + } + + String name = dnsName.substring(0, i); + dnsName = dnsName.substring(i + 1); + sb.append(String.format("%02x", name.length())); + sb.append(WifiP2pServiceInfo.bin2HexStr(name.getBytes())); + } + return sb.toString(); + } +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceRequest.java new file mode 100644 index 0000000..d5415e0 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceRequest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.net.wifi.p2p.WifiP2pManager; + +/** + * A class for creating a Bonjour service discovery request for use with + * {@link WifiP2pManager#addServiceRequest} and {@link WifiP2pManager#removeServiceRequest} + * + * {@see WifiP2pManager} + * {@see WifiP2pServiceRequest} + * {@see WifiP2pUpnpServiceRequest} + */ +public class WifiP2pDnsSdServiceRequest extends WifiP2pServiceRequest { + + /** + * This constructor is only used in newInstance(). + * + * @param query The part of service specific query. + * @hide + */ + private WifiP2pDnsSdServiceRequest(String query) { + super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, query); + } + + /** + * This constructor is only used in newInstance(). + * @hide + */ + private WifiP2pDnsSdServiceRequest() { + super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, null); + } + + private WifiP2pDnsSdServiceRequest(String dnsQuery, int dnsType, int version) { + super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, WifiP2pDnsSdServiceInfo.createRequest( + dnsQuery, + dnsType, + version)); + } + + /** + * Create a service discovery request to search all Bonjour services. + * + * @return service request for Bonjour. + */ + public static WifiP2pDnsSdServiceRequest newInstance() { + return new WifiP2pDnsSdServiceRequest(); + } + + /** + * Create a service discovery to search for Bonjour services with the specified + * service type. + * + * @param serviceType service type. Cannot be null <br> + * "_afpovertcp._tcp."(Apple File Sharing over TCP)<br> + * "_ipp._tcp" (IP Printing over TCP)<br> + * "_http._tcp" (http service) + * @return service request for DnsSd. + */ + public static WifiP2pDnsSdServiceRequest newInstance(String serviceType) { + if (serviceType == null) { + throw new IllegalArgumentException("service type cannot be null"); + } + return new WifiP2pDnsSdServiceRequest(serviceType + ".local.", + WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR, + WifiP2pDnsSdServiceInfo.VERSION_1); + } + + /** + * Create a service discovery request to get the TXT data from the specified + * Bonjour service. + * + * @param instanceName instance name. Cannot be null. <br> + * "MyPrinter" + * @param serviceType service type. Cannot be null. <br> + * e.g) <br> + * "_afpovertcp._tcp"(Apple File Sharing over TCP)<br> + * "_ipp._tcp" (IP Printing over TCP)<br> + * @return service request for Bonjour. + */ + public static WifiP2pDnsSdServiceRequest newInstance(String instanceName, + String serviceType) { + if (instanceName == null || serviceType == null) { + throw new IllegalArgumentException( + "instance name or service type cannot be null"); + } + String fullDomainName = instanceName + "." + serviceType + ".local."; + return new WifiP2pDnsSdServiceRequest(fullDomainName, + WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT, + WifiP2pDnsSdServiceInfo.VERSION_1); + } +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceResponse.java new file mode 100644 index 0000000..c053c8a --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceResponse.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.net.nsd.DnsSdTxtRecord; +import android.net.wifi.p2p.WifiP2pDevice; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * A class for a response of bonjour service discovery. + * + * @hide + */ +public class WifiP2pDnsSdServiceResponse extends WifiP2pServiceResponse { + + /** + * DNS query name. + * e.g) + * for PTR + * "_ipp._tcp.local." + * for TXT + * "MyPrinter._ipp._tcp.local." + */ + private String mDnsQueryName; + + /** + * Service instance name. + * e.g) "MyPrinter" + * This field is only used when the dns type equals to + * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR}. + */ + private String mInstanceName; + + /** + * DNS Type. + * Should be {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR} or + * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}. + */ + private int mDnsType; + + /** + * DnsSd version number. + * Should be {@link WifiP2pDnsSdServiceInfo#VERSION_1}. + */ + private int mVersion; + + /** + * Txt record. + * This field is only used when the dns type equals to + * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}. + */ + private DnsSdTxtRecord mTxtRecord; + + /** + * Virtual memory packet. + * see E.3 of the Wi-Fi Direct technical specification for the detail.<br> + * The spec can be obtained from wi-fi.org + * Key: pointer Value: domain name.<br> + */ + private final static Map<Integer, String> sVmpack; + + static { + sVmpack = new HashMap<Integer, String>(); + sVmpack.put(0x0c, "_tcp.local."); + sVmpack.put(0x11, "local."); + sVmpack.put(0x1c, "_udp.local."); + } + + /** + * Returns query DNS name. + * @return DNS name. + */ + public String getDnsQueryName() { + return mDnsQueryName; + } + + /** + * Return query DNS type. + * @return DNS type. + */ + public int getDnsType() { + return mDnsType; + } + + /** + * Return bonjour version number. + * @return version number. + */ + public int getVersion() { + return mVersion; + } + + /** + * Return instance name. + * @return + */ + public String getInstanceName() { + return mInstanceName; + } + + /** + * Return TXT record data. + * @return TXT record data. + */ + public DnsSdTxtRecord getTxtRecord() { + return mTxtRecord; + } + + @Override + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("serviceType:DnsSd(").append(mServiceType).append(")"); + sbuf.append(" status:").append(Status.toString(mStatus)); + sbuf.append(" srcAddr:").append(mDevice.deviceAddress); + sbuf.append(" version:").append(String.format("%02x", mVersion)); + sbuf.append(" dnsName:").append(mDnsQueryName); + if (mTxtRecord != null) { + sbuf.append(" TxtRecord:").append(mTxtRecord); + } + if (mInstanceName != null) { + sbuf.append(" InsName:").append(mInstanceName); + } + return sbuf.toString(); + } + + /** + * This is only used in framework. + * @param status status code. + * @param dev source device. + * @param data RDATA. + * @hide + */ + protected WifiP2pDnsSdServiceResponse(int status, + int tranId, WifiP2pDevice dev, byte[] data) { + super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, + status, tranId, dev, data); + if (!parse()) { + throw new IllegalArgumentException("Malformed bonjour service response"); + } + } + + /** + * Parse DnsSd service discovery response. + * + * @return {@code true} if the operation succeeded + */ + private boolean parse() { + /* + * The data format from Wi-Fi Direct spec is as follows. + * ________________________________________________ + * | encoded and compressed dns name (variable) | + * ________________________________________________ + * | dnstype(2byte) | version(1byte) | + * ________________________________________________ + * | RDATA (variable) | + */ + if (mData == null) { + // the empty is OK. + return true; + } + + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mData)); + + mDnsQueryName = readDnsName(dis); + if (mDnsQueryName == null) { + return false; + } + + try { + mDnsType = dis.readUnsignedShort(); + mVersion = dis.readUnsignedByte(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR) { + String rData = readDnsName(dis); + if (rData == null) { + return false; + } + if (rData.length() <= mDnsQueryName.length()) { + return false; + } + + mInstanceName = rData.substring(0, + rData.length() - mDnsQueryName.length() -1); + } else if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) { + mTxtRecord = readTxtData(dis); + if (mTxtRecord == null) { + return false; + } + } else { + return false; + } + + return true; + } + + /** + * Read dns name. + * + * @param dis data input stream. + * @return dns name + */ + private String readDnsName(DataInputStream dis) { + StringBuffer sb = new StringBuffer(); + + // copy virtual memory packet. + HashMap<Integer, String> vmpack = new HashMap<Integer, String>(sVmpack); + if (mDnsQueryName != null) { + vmpack.put(0x27, mDnsQueryName); + } + try { + while (true) { + int i = dis.readUnsignedByte(); + if (i == 0x00) { + return sb.toString(); + } else if (i == 0xc0) { + // refer to pointer. + String ref = vmpack.get(dis.readUnsignedByte()); + if (ref == null) { + //invalid. + return null; + } + sb.append(ref); + return sb.toString(); + } else { + byte[] data = new byte[i]; + dis.readFully(data); + sb.append(new String(data)); + sb.append("."); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Read TXT record data. + * + * @param dis + * @return TXT record data + */ + private DnsSdTxtRecord readTxtData(DataInputStream dis) { + DnsSdTxtRecord txtRecord = new DnsSdTxtRecord(); + try { + while (dis.available() > 0) { + int len = dis.readUnsignedByte(); + if (len == 0) { + break; + } + byte[] data = new byte[len]; + dis.readFully(data); + String[] keyVal = new String(data).split("="); + if (keyVal.length != 2) { + return null; + } + txtRecord.set(keyVal[0], keyVal[1]); + } + return txtRecord; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Creates DnsSd service response. + * This is only called from WifiP2pServiceResponse + * + * @param status status code. + * @param dev source device. + * @param data DnsSd response data. + * @return DnsSd service response data. + * @hide + */ + static WifiP2pDnsSdServiceResponse newInstance(int status, + int transId, WifiP2pDevice dev, byte[] data) { + if (status != WifiP2pServiceResponse.Status.SUCCESS) { + return new WifiP2pDnsSdServiceResponse(status, + transId, dev, null); + } + try { + return new WifiP2pDnsSdServiceResponse(status, + transId, dev, data); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl new file mode 100644 index 0000000..cf2cb4a --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.servicediscovery; + +parcelable WifiP2pServiceInfo; diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java new file mode 100644 index 0000000..b931475 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class for storing service information that is advertised + * over a Wi-Fi peer-to-peer setup + * + * @see WifiP2pUpnpServiceInfo + * @see WifiP2pDnsSdServiceInfo + */ +public class WifiP2pServiceInfo implements Parcelable { + + /** + * All service protocol types. + */ + public static final int SERVICE_TYPE_ALL = 0; + + /** + * DNS based service discovery protocol. + */ + public static final int SERVICE_TYPE_BONJOUR = 1; + + /** + * UPnP protocol. + */ + public static final int SERVICE_TYPE_UPNP = 2; + + /** + * WS-Discovery protocol + * @hide + */ + public static final int SERVICE_TYPE_WS_DISCOVERY = 3; + + /** + * Vendor Specific protocol + */ + public static final int SERVICE_TYPE_VENDOR_SPECIFIC = 255; + + /** + * the list of query string for wpa_supplicant + * + * e.g) + * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.) + * {"bonjour", "045f697070c00c000c01", "094d795072696e746572c027" + * + * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript) + * {"bonjour", "096d797072696e746572045f697070c00c001001", + * "09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074"} + * + * [UPnP] + * # UPnP uuid + * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012"} + * + * # UPnP rootdevice + * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"} + * + * # UPnP device + * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp + * -org:device:InternetGatewayDevice:1"} + * + * # UPnP service + * {"upnp", "10", "uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp + * -org:service:ContentDirectory:2"} + */ + private List<String> mQueryList; + + /** + * This is only used in subclass. + * + * @param queryList query string for wpa_supplicant + * @hide + */ + protected WifiP2pServiceInfo(List<String> queryList) { + if (queryList == null) { + throw new IllegalArgumentException("query list cannot be null"); + } + mQueryList = queryList; + } + + /** + * Return the list of the query string for wpa_supplicant. + * + * @return the list of the query string for wpa_supplicant. + * @hide + */ + public List<String> getSupplicantQueryList() { + return mQueryList; + } + + /** + * Converts byte array to hex string. + * + * @param data + * @return hex string. + * @hide + */ + static String bin2HexStr(byte[] data) { + StringBuffer sb = new StringBuffer(); + + for (byte b: data) { + String s = null; + try { + s = Integer.toHexString(b & 0xff); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + //add 0 padding + if (s.length() == 1) { + sb.append('0'); + } + sb.append(s); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof WifiP2pServiceInfo)) { + return false; + } + + WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)o; + return mQueryList.equals(servInfo.mQueryList); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mQueryList == null ? 0 : mQueryList.hashCode()); + return result; + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeStringList(mQueryList); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pServiceInfo> CREATOR = + new Creator<WifiP2pServiceInfo>() { + public WifiP2pServiceInfo createFromParcel(Parcel in) { + + List<String> data = new ArrayList<String>(); + in.readStringList(data); + return new WifiP2pServiceInfo(data); + } + + public WifiP2pServiceInfo[] newArray(int size) { + return new WifiP2pServiceInfo[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl new file mode 100644 index 0000000..d5a1e8f --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.servicediscovery; + +parcelable WifiP2pServiceRequest; diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java new file mode 100644 index 0000000..c7f0e5f --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.net.wifi.p2p.WifiP2pManager; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class for creating a service discovery request for use with + * {@link WifiP2pManager#addServiceRequest} and {@link WifiP2pManager#removeServiceRequest} + * + * <p>This class is used to create service discovery request for custom + * vendor specific service discovery protocol {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC} + * or to search all service protocols {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL}. + * + * <p>For the purpose of creating a UPnP or Bonjour service request, use + * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest} respectively. + * + * {@see WifiP2pManager} + * {@see WifiP2pUpnpServiceRequest} + * {@see WifiP2pDnsSdServiceRequest} + */ +public class WifiP2pServiceRequest implements Parcelable { + + /** + * Service discovery protocol. It's defined in table63 in Wi-Fi Direct specification. + */ + private int mProtocolType; + + /** + * The length of the service request TLV. + * The value is equal to 2 plus the number of octets in the + * query data field. + */ + private int mLength; + + /** + * Service transaction ID. + * This is a nonzero value used to match the service request/response TLVs. + */ + private int mTransId; + + /** + * The hex dump string of query data for the requested service information. + * + * e.g) DnsSd apple file sharing over tcp (dns name=_afpovertcp._tcp.local.) + * 0b5f6166706f766572746370c00c000c01 + */ + private String mQuery; + + /** + * This constructor is only used in newInstance(). + * + * @param protocolType service discovery protocol. + * @param query The part of service specific query. + * @hide + */ + protected WifiP2pServiceRequest(int protocolType, String query) { + validateQuery(query); + + mProtocolType = protocolType; + mQuery = query; + if (query != null) { + mLength = query.length()/2 + 2; + } else { + mLength = 2; + } + } + + /** + * This constructor is only used in Parcelable. + * + * @param serviceType service discovery type. + * @param length the length of service discovery packet. + * @param transId the transaction id + * @param query The part of service specific query. + */ + private WifiP2pServiceRequest(int serviceType, int length, + int transId, String query) { + mProtocolType = serviceType; + mLength = length; + mTransId = transId; + mQuery = query; + } + + /** + * Return transaction id. + * + * @return transaction id + * @hide + */ + public int getTransactionId() { + return mTransId; + } + + /** + * Set transaction id. + * + * @param id + * @hide + */ + public void setTransactionId(int id) { + mTransId = id; + } + + /** + * Return wpa_supplicant request string. + * + * The format is the hex dump of the following frame. + * <pre> + * _______________________________________________________________ + * | Length (2) | Type (1) | Transaction ID (1) | + * | Query Data (variable) | + * </pre> + * + * @return wpa_supplicant request string. + * @hide + */ + public String getSupplicantQuery() { + StringBuffer sb = new StringBuffer(); + // length is retained as little endian format. + sb.append(String.format("%02x", (mLength) & 0xff)); + sb.append(String.format("%02x", (mLength >> 8) & 0xff)); + sb.append(String.format("%02x", mProtocolType)); + sb.append(String.format("%02x", mTransId)); + if (mQuery != null) { + sb.append(mQuery); + } + + return sb.toString(); + } + + /** + * Validate query. + * + * <p>If invalid, throw IllegalArgumentException. + * @param query The part of service specific query. + */ + private void validateQuery(String query) { + if (query == null) { + return; + } + + int UNSIGNED_SHORT_MAX = 0xffff; + if (query.length()%2 == 1) { + throw new IllegalArgumentException( + "query size is invalid. query=" + query); + } + if (query.length()/2 > UNSIGNED_SHORT_MAX) { + throw new IllegalArgumentException( + "query size is too large. len=" + query.length()); + } + + // check whether query is hex string. + query = query.toLowerCase(); + char[] chars = query.toCharArray(); + for (char c: chars) { + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f'))){ + throw new IllegalArgumentException( + "query should be hex string. query=" + query); + } + } + } + + /** + * Create a service discovery request. + * + * @param protocolType can be {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL} + * or {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}. + * In order to create a UPnP or Bonjour service request, use + * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest} + * respectively + * + * @param queryData hex string that is vendor specific. Can be null. + * @return service discovery request. + */ + public static WifiP2pServiceRequest newInstance(int protocolType, String queryData) { + return new WifiP2pServiceRequest(protocolType, queryData); + } + + /** + * Create a service discovery request. + * + * @param protocolType can be {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL} + * or {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}. + * In order to create a UPnP or Bonjour service request, use + * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest} + * respectively + * + * @return service discovery request. + */ + public static WifiP2pServiceRequest newInstance(int protocolType ) { + return new WifiP2pServiceRequest(protocolType, null); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof WifiP2pServiceRequest)) { + return false; + } + + WifiP2pServiceRequest req = (WifiP2pServiceRequest)o; + + /* + * Not compare transaction id. + * Transaction id may be changed on each service discovery operation. + */ + if ((req.mProtocolType != mProtocolType) || + (req.mLength != mLength)) { + return false; + } + + if (req.mQuery == null && mQuery == null) { + return true; + } else if (req.mQuery != null) { + return req.mQuery.equals(mQuery); + } + return false; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mProtocolType; + result = 31 * result + mLength; + result = 31 * result + (mQuery == null ? 0 : mQuery.hashCode()); + return result; + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mProtocolType); + dest.writeInt(mLength); + dest.writeInt(mTransId); + dest.writeString(mQuery); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pServiceRequest> CREATOR = + new Creator<WifiP2pServiceRequest>() { + public WifiP2pServiceRequest createFromParcel(Parcel in) { + int servType = in.readInt(); + int length = in.readInt(); + int transId = in.readInt(); + String query = in.readString(); + return new WifiP2pServiceRequest(servType, length, transId, query); + } + + public WifiP2pServiceRequest[] newArray(int size) { + return new WifiP2pServiceRequest[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl new file mode 100644 index 0000000..c81d1f9 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.servicediscovery; + +parcelable WifiP2pServiceResponse; diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java new file mode 100644 index 0000000..ac31663 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.net.wifi.p2p.WifiP2pDevice; +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The class for a response of service discovery. + * + * @hide + */ +public class WifiP2pServiceResponse implements Parcelable { + + private static int MAX_BUF_SIZE = 1024; + + /** + * Service type. It's defined in table63 in Wi-Fi Direct specification. + */ + protected int mServiceType; + + /** + * Status code of service discovery response. + * It's defined in table65 in Wi-Fi Direct specification. + * @see Status + */ + protected int mStatus; + + /** + * Service transaction ID. + * This is a nonzero value used to match the service request/response TLVs. + */ + protected int mTransId; + + /** + * Source device. + */ + protected WifiP2pDevice mDevice; + + /** + * Service discovery response data based on the requested on + * the service protocol type. The protocol format depends on the service type. + */ + protected byte[] mData; + + + /** + * The status code of service discovery response. + * Currently 4 status codes are defined and the status codes from 4 to 255 + * are reserved. + * + * See Wi-Fi Direct specification for the detail. + */ + public static class Status { + /** success */ + public static final int SUCCESS = 0; + + /** the service protocol type is not available */ + public static final int SERVICE_PROTOCOL_NOT_AVAILABLE = 1; + + /** the requested information is not available */ + public static final int REQUESTED_INFORMATION_NOT_AVAILABLE = 2; + + /** bad request */ + public static final int BAD_REQUEST = 3; + + /** @hide */ + public static String toString(int status) { + switch(status) { + case SUCCESS: + return "SUCCESS"; + case SERVICE_PROTOCOL_NOT_AVAILABLE: + return "SERVICE_PROTOCOL_NOT_AVAILABLE"; + case REQUESTED_INFORMATION_NOT_AVAILABLE: + return "REQUESTED_INFORMATION_NOT_AVAILABLE"; + case BAD_REQUEST: + return "BAD_REQUEST"; + default: + return "UNKNOWN"; + } + } + + /** not used */ + private Status() {} + } + + /** + * Hidden constructor. This is only used in framework. + * + * @param serviceType service discovery type. + * @param status status code. + * @param transId transaction id. + * @param device source device. + * @param data query data. + */ + protected WifiP2pServiceResponse(int serviceType, int status, int transId, + WifiP2pDevice device, byte[] data) { + mServiceType = serviceType; + mStatus = status; + mTransId = transId; + mDevice = device; + mData = data; + } + + /** + * Return the service type of service discovery response. + * + * @return service discovery type.<br> + * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_BONJOUR} + */ + public int getServiceType() { + return mServiceType; + } + + /** + * Return the status code of service discovery response. + * + * @return status code. + * @see Status + */ + public int getStatus() { + return mStatus; + } + + /** + * Return the transaction id of service discovery response. + * + * @return transaction id. + * @hide + */ + public int getTransactionId() { + return mTransId; + } + + /** + * Return response data. + * + * <pre>Data format depends on service type + * + * @return a query or response data. + */ + public byte[] getRawData() { + return mData; + } + + /** + * Returns the source device of service discovery response. + * + * <pre>This is valid only when service discovery response. + * + * @return the source device of service discovery response. + */ + public WifiP2pDevice getSrcDevice() { + return mDevice; + } + + /** @hide */ + public void setSrcDevice(WifiP2pDevice dev) { + if (dev == null) return; + this.mDevice = dev; + } + + + /** + * Create the list of WifiP2pServiceResponse instance from supplicant event. + * + * <pre>The format is as follows. + * P2P-SERV-DISC-RESP <address> <update indicator> <response data> + * e.g) P2P-SERV-DISC-RESP 02:03:7f:11:62:da 1 0300000101 + * + * @param supplicantEvent wpa_supplicant event string. + * @return if parse failed, return null + * @hide + */ + public static List<WifiP2pServiceResponse> newInstance(String supplicantEvent) { + + List<WifiP2pServiceResponse> respList = new ArrayList<WifiP2pServiceResponse>(); + String[] args = supplicantEvent.split(" "); + if (args.length != 4) { + return null; + } + WifiP2pDevice dev = new WifiP2pDevice(); + String srcAddr = args[1]; + dev.deviceAddress = srcAddr; + //String updateIndicator = args[2];//not used. + byte[] bin = hexStr2Bin(args[3]); + if (bin == null) { + return null; + } + + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bin)); + try { + while (dis.available() > 0) { + /* + * Service discovery header is as follows. + * ______________________________________________________________ + * | Length(2byte) | Type(1byte) | TransId(1byte)}| + * ______________________________________________________________ + * | status(1byte) | vendor specific(variable) | + */ + // The length equals to 3 plus the number of octets in the vendor + // specific content field. And this is little endian. + int length = ((dis.readByte() & 0xff) + + ((dis.readByte() & 0xff) << 8)) - 3; + int type = dis.readUnsignedByte(); + byte transId = dis.readByte(); + int status = dis.readUnsignedByte(); + if (length < 0) { + return null; + } + if (length == 0) { + if (status == Status.SUCCESS) { + respList.add(new WifiP2pServiceResponse(type, status, + transId, dev, null)); + } + continue; + } + if (length > MAX_BUF_SIZE) { + dis.skip(length); + continue; + } + byte[] data = new byte[length]; + dis.readFully(data); + + WifiP2pServiceResponse resp; + if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) { + resp = WifiP2pDnsSdServiceResponse.newInstance(status, + transId, dev, data); + } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) { + resp = WifiP2pUpnpServiceResponse.newInstance(status, + transId, dev, data); + } else { + resp = new WifiP2pServiceResponse(type, status, transId, dev, data); + } + if (resp != null && resp.getStatus() == Status.SUCCESS) { + respList.add(resp); + } + } + return respList; + } catch (IOException e) { + e.printStackTrace(); + } + + if (respList.size() > 0) { + return respList; + } + return null; + } + + /** + * Converts hex string to byte array. + * + * @param hex hex string. if invalid, return null. + * @return binary data. + */ + private static byte[] hexStr2Bin(String hex) { + int sz = hex.length()/2; + byte[] b = new byte[hex.length()/2]; + + for (int i=0;i<sz;i++) { + try { + b[i] = (byte)Integer.parseInt(hex.substring(i*2, i*2+2), 16); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + return b; + } + + @Override + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("serviceType:").append(mServiceType); + sbuf.append(" status:").append(Status.toString(mStatus)); + sbuf.append(" srcAddr:").append(mDevice.deviceAddress); + sbuf.append(" data:").append(mData); + return sbuf.toString(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof WifiP2pServiceResponse)) { + return false; + } + + WifiP2pServiceResponse req = (WifiP2pServiceResponse)o; + + return (req.mServiceType == mServiceType) && + (req.mStatus == mStatus) && + equals(req.mDevice.deviceAddress, mDevice.deviceAddress) && + Arrays.equals(req.mData, mData); + } + + private boolean equals(Object a, Object b) { + if (a == null && b == null) { + return true; + } else if (a != null) { + return a.equals(b); + } + return false; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mServiceType; + result = 31 * result + mStatus; + result = 31 * result + mTransId; + result = 31 * result + (mDevice.deviceAddress == null ? + 0 : mDevice.deviceAddress.hashCode()); + result = 31 * result + (mData == null ? 0 : mData.hashCode()); + return result; + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mServiceType); + dest.writeInt(mStatus); + dest.writeInt(mTransId); + dest.writeParcelable(mDevice, flags); + if (mData == null || mData.length == 0) { + dest.writeInt(0); + } else { + dest.writeInt(mData.length); + dest.writeByteArray(mData); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pServiceResponse> CREATOR = + new Creator<WifiP2pServiceResponse>() { + public WifiP2pServiceResponse createFromParcel(Parcel in) { + + int type = in.readInt(); + int status = in.readInt(); + int transId = in.readInt(); + WifiP2pDevice dev = (WifiP2pDevice)in.readParcelable(null); + int len = in.readInt(); + byte[] data = null; + if (len > 0) { + data = new byte[len]; + in.readByteArray(data); + } + if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) { + return WifiP2pDnsSdServiceResponse.newInstance(status, + transId, dev, data); + } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) { + return WifiP2pUpnpServiceResponse.newInstance(status, + transId, dev, data); + } + return new WifiP2pServiceResponse(type, status, transId, dev, data); + } + + public WifiP2pServiceResponse[] newArray(int size) { + return new WifiP2pServiceResponse[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java new file mode 100644 index 0000000..40a0d61 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * A class for storing Upnp service information that is advertised + * over a Wi-Fi peer-to-peer setup. + * + * {@see android.net.wifi.p2p.WifiP2pManager#addLocalService} + * {@see android.net.wifi.p2p.WifiP2pManager#removeLocalService} + * {@see WifiP2pServiceInfo} + * {@see WifiP2pDnsSdServiceInfo} + */ +public class WifiP2pUpnpServiceInfo extends WifiP2pServiceInfo { + + /** + * UPnP version 1.0. + * + * <pre>Query Version should always be set to 0x10 if the query values are + * compatible with UPnP Device Architecture 1.0. + * @hide + */ + public static final int VERSION_1_0 = 0x10; + + /** + * This constructor is only used in newInstance(). + * + * @param queryList + */ + private WifiP2pUpnpServiceInfo(List<String> queryList) { + super(queryList); + } + + /** + * Create UPnP service information object. + * + * @param uuid a string representation of this UUID in the following format, + * as per <a href="http://www.ietf.org/rfc/rfc4122.txt">RFC 4122</a>.<br> + * e.g) 6859dede-8574-59ab-9332-123456789012 + * @param device a string representation of this device in the following format, + * as per + * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf"> + * UPnP Device Architecture1.1</a><br> + * e.g) urn:schemas-upnp-org:device:MediaServer:1 + * @param services a string representation of this service in the following format, + * as per + * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf"> + * UPnP Device Architecture1.1</a><br> + * e.g) urn:schemas-upnp-org:service:ContentDirectory:1 + * @return UPnP service information object. + */ + public static WifiP2pUpnpServiceInfo newInstance(String uuid, + String device, List<String> services) { + if (uuid == null || device == null) { + throw new IllegalArgumentException("uuid or device cannnot be null"); + } + UUID.fromString(uuid); + + ArrayList<String> info = new ArrayList<String>(); + + info.add(createSupplicantQuery(uuid, null)); + info.add(createSupplicantQuery(uuid, "upnp:rootdevice")); + info.add(createSupplicantQuery(uuid, device)); + if (services != null) { + for (String service:services) { + info.add(createSupplicantQuery(uuid, service)); + } + } + + return new WifiP2pUpnpServiceInfo(info); + } + + /** + * Create wpa_supplicant service query for upnp. + * + * @param uuid + * @param data + * @return wpa_supplicant service query for upnp + */ + private static String createSupplicantQuery(String uuid, String data) { + StringBuffer sb = new StringBuffer(); + sb.append("upnp "); + sb.append(String.format("%02x ", VERSION_1_0)); + sb.append("uuid:"); + sb.append(uuid); + if (data != null) { + sb.append("::"); + sb.append(data); + } + return sb.toString(); + } +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java new file mode 100644 index 0000000..b5cf144 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.net.wifi.p2p.WifiP2pManager; + +/** + * A class for creating a Upnp service discovery request for use with + * {@link WifiP2pManager#addServiceRequest} and {@link WifiP2pManager#removeServiceRequest} + * + * {@see WifiP2pManager} + * {@see WifiP2pServiceRequest} + * {@see WifiP2pDnsSdServiceRequest} + */ +public class WifiP2pUpnpServiceRequest extends WifiP2pServiceRequest { + + /** + * This constructor is only used in newInstance(). + * + * @param query The part of service specific query. + * @hide + */ + protected WifiP2pUpnpServiceRequest(String query) { + super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, query); + } + + /** + * This constructor is only used in newInstance(). + * @hide + */ + protected WifiP2pUpnpServiceRequest() { + super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, null); + } + + /** + * Create a service discovery request to search all UPnP services. + * + * @return service request for UPnP. + */ + public static WifiP2pUpnpServiceRequest newInstance() { + return new WifiP2pUpnpServiceRequest(); + } + /** + * Create a service discovery request to search specified UPnP services. + * + * @param st ssdp search target. Cannot be null.<br> + * e.g ) <br> + * <ul> + * <li>"ssdp:all" + * <li>"upnp:rootdevice" + * <li>"urn:schemas-upnp-org:device:MediaServer:2" + * <li>"urn:schemas-upnp-org:service:ContentDirectory:2" + * <li>"uuid:6859dede-8574-59ab-9332-123456789012" + * </ul> + * @return service request for UPnP. + */ + public static WifiP2pUpnpServiceRequest newInstance(String st) { + if (st == null) { + throw new IllegalArgumentException("search target cannot be null"); + } + StringBuffer sb = new StringBuffer(); + sb.append(String.format("%02x", WifiP2pUpnpServiceInfo.VERSION_1_0)); + sb.append(WifiP2pServiceInfo.bin2HexStr(st.getBytes())); + return new WifiP2pUpnpServiceRequest(sb.toString()); + } +} diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java new file mode 100644 index 0000000..ab95af6 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.p2p.nsd; + +import android.net.wifi.p2p.WifiP2pDevice; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class for a response of upnp service discovery. + * + * @hide + */ +public class WifiP2pUpnpServiceResponse extends WifiP2pServiceResponse { + + /** + * UPnP version. should be {@link WifiP2pUpnpServiceInfo#VERSION_1_0} + */ + private int mVersion; + + /** + * The list of Unique Service Name. + * e.g) + *{"uuid:6859dede-8574-59ab-9332-123456789012", + *"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"} + */ + private List<String> mUniqueServiceNames; + + /** + * Return UPnP version number. + * + * @return version number. + * @see WifiP2pUpnpServiceInfo#VERSION_1_0 + */ + public int getVersion() { + return mVersion; + } + + /** + * Return Unique Service Name strings. + * + * @return Unique Service Name.<br> + * e.g ) <br> + * <ul> + * <li>"uuid:6859dede-8574-59ab-9332-123456789012" + * <li>"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice" + * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device: + * MediaServer:2" + * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:service: + * ContentDirectory:2" + * </ul> + */ + public List<String> getUniqueServiceNames() { + return mUniqueServiceNames; + } + + /** + * hidden constructor. + * + * @param status status code + * @param transId transaction id + * @param dev source device + * @param data UPnP response data. + */ + protected WifiP2pUpnpServiceResponse(int status, + int transId, WifiP2pDevice dev, byte[] data) { + super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, + status, transId, dev, data); + if (!parse()) { + throw new IllegalArgumentException("Malformed upnp service response"); + } + } + + /** + * Parse UPnP service discovery response + * + * @return {@code true} if the operation succeeded + */ + private boolean parse() { + /* + * The data format is as follows. + * + * ______________________________________________________ + * | Version (1) | USN (Variable) | + */ + if (mData == null) { + // the empty is OK. + return true; + } + + if (mData.length < 1) { + return false; + } + + mVersion = mData[0] & 0xff; + String[] names = new String(mData, 1, mData.length-1).split(","); + mUniqueServiceNames = new ArrayList<String>(); + for (String name : names) { + mUniqueServiceNames.add(name); + } + return true; + } + + @Override + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("serviceType:UPnP(").append(mServiceType).append(")"); + sbuf.append(" status:").append(Status.toString(mStatus)); + sbuf.append(" srcAddr:").append(mDevice.deviceAddress); + sbuf.append(" version:").append(String.format("%02x", mVersion)); + if (mUniqueServiceNames != null) { + for (String name : mUniqueServiceNames) { + sbuf.append(" usn:").append(name); + } + } + return sbuf.toString(); + } + + /** + * Create upnp service response. + * + * <pre>This is only used in{@link WifiP2pServiceResponse} + * + * @param status status code. + * @param transId transaction id. + * @param device source device. + * @param data UPnP response data. + * @return UPnP service response data. + * @hide + */ + static WifiP2pUpnpServiceResponse newInstance(int status, + int transId, WifiP2pDevice device, byte[] data) { + if (status != WifiP2pServiceResponse.Status.SUCCESS) { + return new WifiP2pUpnpServiceResponse(status, transId, device, null); + } + + try { + return new WifiP2pUpnpServiceResponse(status, transId, device, data); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + return null; + } +} |
