diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /wifi | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'wifi')
-rw-r--r-- | wifi/java/android/net/wifi/IWifiManager.aidl | 67 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/ScanResult.aidl | 19 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/ScanResult.java | 116 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/SupplicantState.java | 177 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiConfiguration.aidl | 19 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiConfiguration.java | 351 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiInfo.aidl | 19 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiInfo.java | 281 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiManager.java | 745 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiMonitor.java | 373 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiNative.java | 123 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiStateTracker.java | 1710 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/package.html | 12 |
13 files changed, 4012 insertions, 0 deletions
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl new file mode 100644 index 0000000..5a9f75c --- /dev/null +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2008, 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 android.net.wifi.WifiInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.ScanResult; +import android.net.DhcpInfo; + +/** + * Interface that allows controlling and querying Wi-Fi connectivity. + * + * {@hide} + */ +interface IWifiManager +{ + List<WifiConfiguration> getConfiguredNetworks(); + + int addOrUpdateNetwork(in WifiConfiguration config); + + boolean removeNetwork(int netId); + + boolean enableNetwork(int netId, boolean disableOthers); + + boolean disableNetwork(int netId); + + boolean pingSupplicant(); + + boolean startScan(); + + List<ScanResult> getScanResults(); + + boolean disconnect(); + + boolean reconnect(); + + boolean reassociate(); + + WifiInfo getConnectionInfo(); + + boolean setWifiEnabled(boolean enable); + + int getWifiState(); + + boolean saveConfiguration(); + + DhcpInfo getDhcpInfo(); + + boolean acquireWifiLock(IBinder lock, String tag); + + boolean releaseWifiLock(IBinder lock); +} + diff --git a/wifi/java/android/net/wifi/ScanResult.aidl b/wifi/java/android/net/wifi/ScanResult.aidl new file mode 100644 index 0000000..bb66722 --- /dev/null +++ b/wifi/java/android/net/wifi/ScanResult.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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; + +parcelable ScanResult; diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java new file mode 100644 index 0000000..32261de --- /dev/null +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2008 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 android.os.Parcelable; +import android.os.Parcel; + +/** + * Describes information about a detected access point. In addition + * to the attributes described here, the supplicant keeps track of + * {@code quality}, {@code noise}, and {@code maxbitrate} attributes, + * but does not currently report them to external clients. + */ +public class ScanResult implements Parcelable { + /** The network name. */ + public String SSID; + /** The address of the access point. */ + public String BSSID; + /** + * Describes the authentication, key management, and encryption schemes + * supported by the access point. + */ + public String capabilities; + /** + * The detected signal level in dBm. At least those are the units used by + * the TI driver. + */ + public int level; + /** + * The frequency in MHz of the channel over which the client is communicating + * with the access point. + */ + public int frequency; + + /** + * We'd like to obtain the following attributes, + * but they are not reported via the socket + * interface, even though they are known + * internally by wpa_supplicant. + * {@hide} + */ + public ScanResult(String SSID, String BSSID, String caps, int level, int frequency) { + this.SSID = SSID; + this.BSSID = BSSID; + this.capabilities = caps; + this.level = level; + this.frequency = frequency; + //networkConfig = null; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + String none = "<none>"; + + sb.append("SSID: "). + append(SSID == null ? none : SSID). + append(", BSSID: "). + append(BSSID == null ? none : BSSID). + append(", capabilities: "). + append(capabilities == null ? none : capabilities). + append(", level: "). + append(level). + append(", frequency: "). + append(frequency); + + return sb.toString(); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(SSID); + dest.writeString(BSSID); + dest.writeString(capabilities); + dest.writeInt(level); + dest.writeInt(frequency); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<ScanResult> CREATOR = + new Creator<ScanResult>() { + public ScanResult createFromParcel(Parcel in) { + return new ScanResult( + in.readString(), + in.readString(), + in.readString(), + in.readInt(), + in.readInt() + ); + } + + public ScanResult[] newArray(int size) { + return new ScanResult[size]; + } + }; + +} diff --git a/wifi/java/android/net/wifi/SupplicantState.java b/wifi/java/android/net/wifi/SupplicantState.java new file mode 100644 index 0000000..169b2d6 --- /dev/null +++ b/wifi/java/android/net/wifi/SupplicantState.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2008 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 android.os.Parcel; +import android.os.Parcelable; + +/** + * From <code>defs.h</code> in <code>wpa_supplicant</code>. + * <p/> + * These enumeration values are used to indicate the current wpa_supplicant + * state. This is more fine-grained than most users will be interested in. + * In general, it is better to use + * {@link android.net.NetworkInfo.State NetworkInfo.State}. + * <p/> + * Note, the order of these enum constants must match the numerical values of the + * state constants in <code>defs.h</code> in <code>wpa_supplicant</code>. + */ +public enum SupplicantState implements Parcelable { + /** + * This state indicates that client is not associated, but is likely to + * start looking for an access point. This state is entered when a + * connection is lost. + */ + DISCONNECTED, + + /** + * Inactive state (wpa_supplicant disabled). + * <p/> + * This state is entered if there are no enabled networks in the + * configuration. wpa_supplicant is not trying to associate with a new + * network and external interaction (e.g., ctrl_iface call to add or + * enable a network) is needed to start association. + */ + INACTIVE, + + /** + * Scanning for a network. + * <p/> + * This state is entered when wpa_supplicant starts scanning for a + * network. + */ + SCANNING, + + /** + * Trying to associate with a BSS/SSID. + * <p/> + * This state is entered when wpa_supplicant has found a suitable BSS + * to associate with and the driver is configured to try to associate + * with this BSS in ap_scan=1 mode. When using ap_scan=2 mode, this + * state is entered when the driver is configured to try to associate + * with a network using the configured SSID and security policy. + */ + ASSOCIATING, + + /** + * Association completed. + * <p/> + * This state is entered when the driver reports that association has + * been successfully completed with an AP. If IEEE 802.1X is used + * (with or without WPA/WPA2), wpa_supplicant remains in this state + * until the IEEE 802.1X/EAPOL authentication has been completed. + */ + ASSOCIATED, + + /** + * WPA 4-Way Key Handshake in progress. + * <p/> + * This state is entered when WPA/WPA2 4-Way Handshake is started. In + * case of WPA-PSK, this happens when receiving the first EAPOL-Key + * frame after association. In case of WPA-EAP, this state is entered + * when the IEEE 802.1X/EAPOL authentication has been completed. + */ + FOUR_WAY_HANDSHAKE, + + /** + * WPA Group Key Handshake in progress. + * <p/> + * This state is entered when 4-Way Key Handshake has been completed + * (i.e., when the supplicant sends out message 4/4) and when Group + * Key rekeying is started by the AP (i.e., when supplicant receives + * message 1/2). + */ + GROUP_HANDSHAKE, + + /** + * All authentication completed. + * <p/> + * This state is entered when the full authentication process is + * completed. In case of WPA2, this happens when the 4-Way Handshake is + * successfully completed. With WPA, this state is entered after the + * Group Key Handshake; with IEEE 802.1X (non-WPA) connection is + * completed after dynamic keys are received (or if not used, after + * the EAP authentication has been completed). With static WEP keys and + * plaintext connections, this state is entered when an association + * has been completed. + * <p/> + * This state indicates that the supplicant has completed its + * processing for the association phase and that data connection is + * fully configured. Note, however, that there may not be any IP + * address associated with the connection yet. Typically, a DHCP + * request needs to be sent at this point to obtain an address. + */ + COMPLETED, + + /** + * An Android-added state that is reported when a client issues an + * explicit DISCONNECT command. In such a case, the supplicant is + * not only dissociated from the current access point (as for the + * DISCONNECTED state above), but it also does not attempt to connect + * to any access point until a RECONNECT or REASSOCIATE command + * is issued by the client. + */ + DORMANT, + + /** + * No connection to wpa_supplicant. + * <p/> + * This is an additional pseudo-state to handle the case where + * wpa_supplicant is not running and/or we have not been able + * to establish a connection to it. + */ + UNINITIALIZED, + + /** + * A pseudo-state that should normally never be seen. + */ + INVALID; + + /** + * Returns {@code true} if the supplicant state is valid and {@code false} + * otherwise. + * @param state The supplicant state + * @return {@code true} if the supplicant state is valid and {@code false} + * otherwise. + */ + public static boolean isValidState(SupplicantState state) { + return state != UNINITIALIZED && state != INVALID; + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(name()); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<SupplicantState> CREATOR = + new Creator<SupplicantState>() { + public SupplicantState createFromParcel(Parcel in) { + return SupplicantState.valueOf(in.readString()); + } + + public SupplicantState[] newArray(int size) { + return new SupplicantState[size]; + } + }; + +} diff --git a/wifi/java/android/net/wifi/WifiConfiguration.aidl b/wifi/java/android/net/wifi/WifiConfiguration.aidl new file mode 100644 index 0000000..237a74d --- /dev/null +++ b/wifi/java/android/net/wifi/WifiConfiguration.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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; + +parcelable WifiConfiguration; diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java new file mode 100644 index 0000000..42a7af7 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2008 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 android.os.Parcelable; +import android.os.Parcel; + +import java.util.BitSet; + +/** + * A class representing a configured Wi-Fi network, including the + * security configuration. Android will not necessarily support + * all of these security schemes initially. + */ +public class WifiConfiguration implements Parcelable { + + /** {@hide} */ + public static final String ssidVarName = "ssid"; + /** {@hide} */ + public static final String bssidVarName = "bssid"; + /** {@hide} */ + public static final String pskVarName = "psk"; + /** {@hide} */ + public static final String[] wepKeyVarNames = { "wep_key0", "wep_key1", "wep_key2", "wep_key3" }; + /** {@hide} */ + public static final String wepTxKeyIdxVarName = "wep_tx_keyidx"; + /** {@hide} */ + public static final String priorityVarName = "priority"; + /** {@hide} */ + public static final String hiddenSSIDVarName = "scan_ssid"; + + /** + * Recognized key management schemes. + */ + public static class KeyMgmt { + private KeyMgmt() { } + + /** WPA is not used; plaintext or static WEP could be used. */ + public static final int NONE = 0; + /** WPA pre-shared key (requires {@code preSharedKey} to be specified). */ + public static final int WPA_PSK = 1; + /** WPA using EAP authentication. Generally used with an external authentication server. */ + public static final int WPA_EAP = 2; + /** IEEE 802.1X using EAP authentication and (optionally) dynamically + * generated WEP keys. */ + public static final int IEEE8021X = 3; + + public static final String varName = "key_mgmt"; + + public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X" }; + } + + /** + * Recognized security protocols. + */ + public static class Protocol { + private Protocol() { } + + /** WPA/IEEE 802.11i/D3.0 */ + public static final int WPA = 0; + /** WPA2/IEEE 802.11i */ + public static final int RSN = 1; + + public static final String varName = "proto"; + + public static final String[] strings = { "WPA", "RSN" }; + } + + /** + * Recognized IEEE 802.11 authentication algorithms. + */ + public static class AuthAlgorithm { + private AuthAlgorithm() { } + + /** Open System authentication (required for WPA/WPA2) */ + public static final int OPEN = 0; + /** Shared Key authentication (requires static WEP keys) */ + public static final int SHARED = 1; + /** LEAP/Network EAP (only used with LEAP) */ + public static final int LEAP = 2; + + public static final String varName = "auth_alg"; + + public static final String[] strings = { "OPEN", "SHARED", "LEAP" }; + } + + /** + * Recognized pairwise ciphers for WPA. + */ + public static class PairwiseCipher { + private PairwiseCipher() { } + + /** Use only Group keys (deprecated) */ + public static final int NONE = 0; + /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */ + public static final int TKIP = 1; + /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */ + public static final int CCMP = 2; + + public static final String varName = "pairwise"; + + public static final String[] strings = { "NONE", "TKIP", "CCMP" }; + } + + /** + * Recognized group ciphers. + * <pre> + * CCMP = AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] + * TKIP = Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] + * WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key + * WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11) + * </pre> + */ + public static class GroupCipher { + private GroupCipher() { } + + /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11) */ + public static final int WEP40 = 0; + /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key */ + public static final int WEP104 = 1; + /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */ + public static final int TKIP = 2; + /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */ + public static final int CCMP = 3; + + public static final String varName = "group"; + + public static final String[] strings = { "WEP40", "WEP104", "TKIP", "CCMP" }; + } + + /** Possible status of a network configuration. */ + public static class Status { + private Status() { } + + /** this is the network we are currently connected to */ + public static final int CURRENT = 0; + /** supplicant will not attempt to use this network */ + public static final int DISABLED = 1; + /** supplicant will consider this network available for association */ + public static final int ENABLED = 2; + + public static final String[] strings = { "current", "disabled", "enabled" }; + } + + /** + * The ID number that the supplicant uses to identify this + * network configuration entry. This must be passed as an argument + * to most calls into the supplicant. + */ + public int networkId; + + /** + * The current status of this network configuration entry. + * @see Status + */ + public int status; + /** + * The network's SSID. Can either be an ASCII string, + * which must be enclosed in double quotation marks + * (e.g., {@code "MyNetwork"}, or a string of + * hex digits,which are not enclosed in quotes + * (e.g., {@code 01a243f405}). + */ + public String SSID; + /** + * When set, this network configuration entry should only be used when + * associating with the AP having the specified BSSID. The value is + * a string in the format of an Ethernet MAC address, e.g., + * <code>XX:XX:XX:XX:XX:XX</code> where each <code>X</code> is a hex digit. + */ + public String BSSID; + + /** + * Pre-shared key for use with WPA-PSK. + * <p/> + * When the value of this key is read, the actual key is + * not returned, just a "*" if the key has a value, or the null + * string otherwise. + */ + public String preSharedKey; + /** + * Up to four WEP keys. Either an ASCII string enclosed in double + * quotation marks (e.g., {@code "abcdef"} or a string + * of hex digits (e.g., {@code 0102030405}). + * <p/> + * When the value of one of these keys is read, the actual key is + * not returned, just a "*" if the key has a value, or the null + * string otherwise. + */ + public String[] wepKeys; + + /** Default WEP key index, ranging from 0 to 3. */ + public int wepTxKeyIndex; + + /** + * Priority determines the preference given to a network by {@code wpa_supplicant} + * when choosing an access point with which to associate. + */ + public int priority; + + /** + * This is a network that does not broadcast its SSID, so an + * SSID-specific probe request must be used for scans. + */ + public boolean hiddenSSID; + + /** + * The set of key management protocols supported by this configuration. + * See {@link KeyMgmt} for descriptions of the values. + * Defaults to WPA-PSK WPA-EAP. + */ + public BitSet allowedKeyManagement; + /** + * The set of security protocols supported by this configuration. + * See {@link Protocol} for descriptions of the values. + * Defaults to WPA RSN. + */ + public BitSet allowedProtocols; + /** + * The set of authentication protocols supported by this configuration. + * See {@link AuthAlgorithm} for descriptions of the values. + * Defaults to automatic selection. + */ + public BitSet allowedAuthAlgorithms; + /** + * The set of pairwise ciphers for WPA supported by this configuration. + * See {@link PairwiseCipher} for descriptions of the values. + * Defaults to CCMP TKIP. + */ + public BitSet allowedPairwiseCiphers; + /** + * The set of group ciphers supported by this configuration. + * See {@link GroupCipher} for descriptions of the values. + * Defaults to CCMP TKIP WEP104 WEP40. + */ + public BitSet allowedGroupCiphers; + + public WifiConfiguration() { + networkId = -1; + SSID = null; + BSSID = null; + priority = 0; + hiddenSSID = false; + allowedKeyManagement = new BitSet(); + allowedProtocols = new BitSet(); + allowedAuthAlgorithms = new BitSet(); + allowedPairwiseCiphers = new BitSet(); + allowedGroupCiphers = new BitSet(); + wepKeys = new String[4]; + for (int i = 0; i < wepKeys.length; i++) + wepKeys[i] = null; + } + + /** + * Construct a WifiConfiguration from a scanned network + * @param scannedAP the scan result used to construct the config entry + * TODO: figure out whether this is a useful way to construct a new entry. + * + public WifiConfiguration(ScanResult scannedAP) { + networkId = -1; + SSID = scannedAP.SSID; + BSSID = scannedAP.BSSID; + } + */ + + private static BitSet readBitSet(Parcel src) { + int cardinality = src.readInt(); + + BitSet set = new BitSet(); + for (int i = 0; i < cardinality; i++) + set.set(src.readInt()); + + return set; + } + + private static void writeBitSet(Parcel dest, BitSet set) { + int nextSetBit = -1; + + dest.writeInt(set.cardinality()); + + while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) + dest.writeInt(nextSetBit); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(networkId); + dest.writeInt(status); + dest.writeString(SSID); + dest.writeString(BSSID); + dest.writeString(preSharedKey); + for (String wepKey : wepKeys) + dest.writeString(wepKey); + dest.writeInt(wepTxKeyIndex); + dest.writeInt(priority); + dest.writeInt(hiddenSSID ? 1 : 0); + + writeBitSet(dest, allowedKeyManagement); + writeBitSet(dest, allowedProtocols); + writeBitSet(dest, allowedAuthAlgorithms); + writeBitSet(dest, allowedPairwiseCiphers); + writeBitSet(dest, allowedGroupCiphers); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiConfiguration> CREATOR = + new Creator<WifiConfiguration>() { + public WifiConfiguration createFromParcel(Parcel in) { + WifiConfiguration config = new WifiConfiguration(); + config.networkId = in.readInt(); + config.status = in.readInt(); + config.SSID = in.readString(); + config.BSSID = in.readString(); + config.preSharedKey = in.readString(); + for (int i = 0; i < config.wepKeys.length; i++) + config.wepKeys[i] = in.readString(); + config.wepTxKeyIndex = in.readInt(); + config.priority = in.readInt(); + config.hiddenSSID = in.readInt() != 0; + config.allowedKeyManagement = readBitSet(in); + config.allowedProtocols = readBitSet(in); + config.allowedAuthAlgorithms = readBitSet(in); + config.allowedPairwiseCiphers = readBitSet(in); + config.allowedGroupCiphers = readBitSet(in); + return config; + } + + public WifiConfiguration[] newArray(int size) { + return new WifiConfiguration[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/WifiInfo.aidl b/wifi/java/android/net/wifi/WifiInfo.aidl new file mode 100644 index 0000000..db47f0b --- /dev/null +++ b/wifi/java/android/net/wifi/WifiInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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; + +parcelable WifiInfo; diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java new file mode 100644 index 0000000..4312bfd --- /dev/null +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2008 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 android.os.Parcelable; +import android.os.Parcel; +import android.net.NetworkInfo.DetailedState; + +import java.util.EnumMap; + +/** + * Describes the state of any Wifi connection that is active or + * is in the process of being set up. + */ +public class WifiInfo implements Parcelable { + /** + * This is the map described in the Javadoc comment above. The positions + * of the elements of the array must correspond to the ordinal values + * of <code>DetailedState</code>. + */ + private static final EnumMap<SupplicantState, DetailedState> stateMap = + new EnumMap<SupplicantState, DetailedState>(SupplicantState.class); + + static { + stateMap.put(SupplicantState.DISCONNECTED, DetailedState.DISCONNECTED); + stateMap.put(SupplicantState.INACTIVE, DetailedState.IDLE); + stateMap.put(SupplicantState.SCANNING, DetailedState.SCANNING); + stateMap.put(SupplicantState.ASSOCIATING, DetailedState.CONNECTING); + stateMap.put(SupplicantState.ASSOCIATED, DetailedState.CONNECTING); + stateMap.put(SupplicantState.FOUR_WAY_HANDSHAKE, DetailedState.AUTHENTICATING); + stateMap.put(SupplicantState.GROUP_HANDSHAKE, DetailedState.AUTHENTICATING); + stateMap.put(SupplicantState.COMPLETED, DetailedState.OBTAINING_IPADDR); + stateMap.put(SupplicantState.DORMANT, DetailedState.DISCONNECTED); + stateMap.put(SupplicantState.UNINITIALIZED, DetailedState.IDLE); + stateMap.put(SupplicantState.INVALID, DetailedState.FAILED); + } + + private SupplicantState mSupplicantState; + private String mBSSID; + private String mSSID; + private int mNetworkId; + private boolean mHiddenSSID; + /** Received Signal Strength Indicator */ + private int mRssi; + + /** Link speed in Mbps */ + public static final String LINK_SPEED_UNITS = "Mbps"; + private int mLinkSpeed; + + private int mIpAddress; + + private String mMacAddress; + + WifiInfo() { + mSSID = null; + mBSSID = null; + mNetworkId = -1; + mSupplicantState = SupplicantState.UNINITIALIZED; + mRssi = -9999; + mLinkSpeed = -1; + mIpAddress = 0; + mHiddenSSID = false; + } + + void setSSID(String SSID) { + mSSID = SSID; + // network is considered not hidden by default + mHiddenSSID = false; + } + + /** + * Returns the service set identifier (SSID) of the current 802.11 network. + * If the SSID is an ASCII string, it will be returned surrounded by double + * quotation marks.Otherwise, it is returned as a string of hex digits. The + * SSID may be {@code null} if there is no network currently connected. + * @return the SSID + */ + public String getSSID() { + return mSSID; + } + + void setBSSID(String BSSID) { + mBSSID = BSSID; + } + + /** + * Return the basic service set identifier (BSSID) of the current access point. + * The BSSID may be {@code null} if there is no network currently connected. + * @return the BSSID, in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX} + */ + public String getBSSID() { + return mBSSID; + } + + /** + * Returns the received signal strength indicator of the current 802.11 + * network. + * <p><strong>This is not normalized, but should be!</strong></p> + * @return the RSSI, in the range ??? to ??? + */ + public int getRssi() { + return mRssi; + } + + void setRssi(int rssi) { + mRssi = rssi; + } + + /** + * Returns the current link speed in {@link #LINK_SPEED_UNITS}. + * @return the link speed. + * @see #LINK_SPEED_UNITS + */ + public int getLinkSpeed() { + return mLinkSpeed; + } + + void setLinkSpeed(int linkSpeed) { + this.mLinkSpeed = linkSpeed; + } + + /** + * Record the MAC address of the WLAN interface + * @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form + */ + void setMacAddress(String macAddress) { + this.mMacAddress = macAddress; + } + + public String getMacAddress() { + return mMacAddress; + } + + void setNetworkId(int id) { + mNetworkId = id; + } + + /** + * Each configured network has a unique small integer ID, used to identify + * the network when performing operations on the supplicant. This method + * returns the ID for the currently connected network. + * @return the network ID, or -1 if there is no currently connected network + */ + public int getNetworkId() { + return mNetworkId; + } + + /** + * Return the detailed state of the supplicant's negotiation with an + * access point, in the form of a {@link SupplicantState SupplicantState} object. + * @return the current {@link SupplicantState SupplicantState} + */ + public SupplicantState getSupplicantState() { + return mSupplicantState; + } + + void setSupplicantState(SupplicantState state) { + mSupplicantState = state; + } + + void setIpAddress(int address) { + mIpAddress = address; + } + + public int getIpAddress() { + return mIpAddress; + } + + /** + * @return {@code true} if this network does not broadcast its SSID, so an + * SSID-specific probe request must be used for scans. + */ + public boolean getHiddenSSID() { + return mHiddenSSID; + } + + /** {@hide} */ + public void setHiddenSSID(boolean hiddenSSID) { + mHiddenSSID = hiddenSSID; + } + + /** + * Map a supplicant state into a fine-grained network connectivity state. + * @param suppState the supplicant state + * @return the corresponding {@link DetailedState} + */ + public static DetailedState getDetailedStateOf(SupplicantState suppState) { + return stateMap.get(suppState); + } + + /** + * Set the <code>SupplicantState</code> from the string name + * of the state. + * @param stateName the name of the state, as a <code>String</code> returned + * in an event sent by {@code wpa_supplicant}. + */ + void setSupplicantState(String stateName) { + mSupplicantState = valueOf(stateName); + } + + static SupplicantState valueOf(String stateName) { + if ("4WAY_HANDSHAKE".equalsIgnoreCase(stateName)) + return SupplicantState.FOUR_WAY_HANDSHAKE; + else { + try { + return SupplicantState.valueOf(stateName.toUpperCase()); + } catch (IllegalArgumentException e) { + return SupplicantState.INVALID; + } + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + String none = "<none>"; + + sb.append("SSID: ").append(mSSID == null ? none : mSSID). + append(", BSSID: ").append(mBSSID == null ? none : mBSSID). + append(", MAC: ").append(mMacAddress == null ? none : mMacAddress). + append(", Supplicant state: "). + append(mSupplicantState == null ? none : mSupplicantState). + append(", RSSI: ").append(mRssi). + append(", Link speed: ").append(mLinkSpeed). + append(", Net ID: ").append(mNetworkId); + + return sb.toString(); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mNetworkId); + dest.writeInt(mRssi); + dest.writeInt(mLinkSpeed); + dest.writeInt(mIpAddress); + dest.writeString(getSSID()); + dest.writeString(mBSSID); + dest.writeString(mMacAddress); + mSupplicantState.writeToParcel(dest, flags); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiInfo> CREATOR = + new Creator<WifiInfo>() { + public WifiInfo createFromParcel(Parcel in) { + WifiInfo info = new WifiInfo(); + info.setNetworkId(in.readInt()); + info.setRssi(in.readInt()); + info.setLinkSpeed(in.readInt()); + info.setIpAddress(in.readInt()); + info.setSSID(in.readString()); + info.mBSSID = in.readString(); + info.mMacAddress = in.readString(); + info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in); + return info; + } + + public WifiInfo[] newArray(int size) { + return new WifiInfo[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java new file mode 100644 index 0000000..f569d5b --- /dev/null +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -0,0 +1,745 @@ +/* + * Copyright (C) 2008 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 android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.net.DhcpInfo; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.RemoteException; + +import com.android.internal.os.RuntimeInit; + +import java.util.List; + +/** + * This class provides the primary API for managing all aspects of Wi-Fi + * connectivity. Get an instance of this class by calling + * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.WIFI_SERVICE)}. + + * It deals with several categories of items: + * <ul> + * <li>The list of configured networks. The list can be viewed and updated, + * and attributes of individual entries can be modified.</li> + * <li>The currently active Wi-Fi network, if any. Connectivity can be + * established or torn down, and dynamic information about the state of + * the network can be queried.</li> + * <li>Results of access point scans, containing enough information to + * make decisions about what access point to connect to.</li> + * <li>It defines the names of various Intent actions that are broadcast + * upon any sort of change in Wi-Fi state. + * </ul> + * This is the API to use when performing Wi-Fi specific operations. To + * perform operations that pertain to network connectivity at an abstract + * level, use {@link android.net.ConnectivityManager}. + */ +public class WifiManager { + + // Supplicant error codes: + /** + * The error code if there was a problem authenticating. + */ + public static final int ERROR_AUTHENTICATING = 1; + + /** + * Broadcast intent action indicating that Wi-Fi has been enabled, disabled, + * enabling, disabling, or unknown. One extra provides this state as an int. + * Another extra provides the previous state, if available. + * + * @see #EXTRA_WIFI_STATE + * @see #EXTRA_PREVIOUS_WIFI_STATE + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String WIFI_STATE_CHANGED_ACTION = + "android.net.wifi.WIFI_STATE_CHANGED"; + /** + * The lookup key for an int that indicates whether Wi-Fi is enabled, + * disabled, enabling, disabling, or unknown. Retrieve it with + * {@link android.content.Intent#getIntExtra(String,int)}. + * + * @see #WIFI_STATE_DISABLED + * @see #WIFI_STATE_DISABLING + * @see #WIFI_STATE_ENABLED + * @see #WIFI_STATE_ENABLING + * @see #WIFI_STATE_UNKNOWN + */ + public static final String EXTRA_WIFI_STATE = "wifi_state"; + /** + * The previous Wi-Fi state. + * + * @see #EXTRA_WIFI_STATE + */ + public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state"; + + /** + * Wi-Fi is currently being disabled. The state will change to {@link #WIFI_STATE_DISABLED} if + * it finishes successfully. + * + * @see #WIFI_STATE_CHANGED_ACTION + * @see #getWifiState() + */ + public static final int WIFI_STATE_DISABLING = 0; + /** + * Wi-Fi is disabled. + * + * @see #WIFI_STATE_CHANGED_ACTION + * @see #getWifiState() + */ + public static final int WIFI_STATE_DISABLED = 1; + /** + * Wi-Fi is currently being enabled. The state will change to {@link #WIFI_STATE_ENABLED} if + * it finishes successfully. + * + * @see #WIFI_STATE_CHANGED_ACTION + * @see #getWifiState() + */ + public static final int WIFI_STATE_ENABLING = 2; + /** + * Wi-Fi is enabled. + * + * @see #WIFI_STATE_CHANGED_ACTION + * @see #getWifiState() + */ + public static final int WIFI_STATE_ENABLED = 3; + /** + * Wi-Fi is in an unknown state. This state will occur when an error happens while enabling + * or disabling. + * + * @see #WIFI_STATE_CHANGED_ACTION + * @see #getWifiState() + */ + public static final int WIFI_STATE_UNKNOWN = 4; + + /** + * Broadcast intent action indicating that a connection to the supplicant has + * been established (and it is now possible + * to perform Wi-Fi operations) or the connection to the supplicant has been + * lost. One extra provides the connection state as a boolean, where {@code true} + * means CONNECTED. + * @see #EXTRA_SUPPLICANT_CONNECTED + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SUPPLICANT_CONNECTION_CHANGE_ACTION = + "android.net.wifi.supplicant.CONNECTION_CHANGE"; + /** + * The lookup key for a boolean that indicates whether a connection to + * the supplicant daemon has been gained or lost. {@code true} means + * a connection now exists. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_SUPPLICANT_CONNECTED = "connected"; + /** + * Broadcast intent action indicating that the state of Wi-Fi connectivity + * has changed. One extra provides the new state + * in the form of a {@link android.net.NetworkInfo} object. If the new state is + * CONNECTED, a second extra may provide the BSSID of the access point, + * as a {@code String}. + * @see #EXTRA_NETWORK_INFO + * @see #EXTRA_BSSID + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE"; + /** + * The lookup key for a {@link android.net.NetworkInfo} object associated with the + * Wi-Fi network. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NETWORK_INFO = "networkInfo"; + /** + * The lookup key for a String giving the BSSID of the access point to which + * we are connected. Only present when the new state is CONNECTED. + * Retrieve with + * {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_BSSID = "bssid"; + /** + * Broadcast intent action indicating that the state of establishing a connection to + * an access point has changed.One extra provides the new + * {@link SupplicantState}. Note that the supplicant state is Wi-Fi specific, and + * is not generally the most useful thing to look at if you are just interested in + * the overall state of connectivity. + * @see #EXTRA_NEW_STATE + * @see #EXTRA_SUPPLICANT_ERROR + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SUPPLICANT_STATE_CHANGED_ACTION = + "android.net.wifi.supplicant.STATE_CHANGE"; + /** + * The lookup key for a {@link SupplicantState} describing the new state + * Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NEW_STATE = "newState"; + + /** + * The lookup key for a {@link SupplicantState} describing the supplicant + * error code if any + * Retrieve with + * {@link android.content.Intent#getIntExtra(String, int)}. + * @see #ERROR_AUTHENTICATING + */ + public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError"; + + /** + * An access point scan has completed, and results are available from the supplicant. + * Call {@link #getScanResults()} to obtain the results. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS"; + /** + * The RSSI (signal strength) has changed. + * @see #EXTRA_NEW_RSSI + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED"; + /** + * The lookup key for an {@code int} giving the new RSSI in dBm. + */ + public static final String EXTRA_NEW_RSSI = "newRssi"; + + /** + * The network IDs of the configured networks could have changed. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED"; + + /** + * Activity Action: Pick a Wi-Fi network to connect to. + * <p>Input: Nothing. + * <p>Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; + + /** Anything worse than or equal to this will show 0 bars. */ + private static final int MIN_RSSI = -100; + + /** Anything better than or equal to this will show the max bars. */ + private static final int MAX_RSSI = -55; + + IWifiManager mService; + Handler mHandler; + + /** Don't allow use of default constructor */ + private WifiManager() { + } + + /** + * Create a new WifiManager instance. + * Applications will almost always want to use + * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve + * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. + * @param service the Binder interface + * @param handler target for messages + * {@hide} - hide this because it takes in a parameter of type IWifiManager, which + * is a system private class. + */ + public WifiManager(IWifiManager service, Handler handler) { + mService = service; + mHandler = handler; + } + + /** + * Return a list of all the networks configured in the supplicant. + * Not all fields of WifiConfiguration are returned. Only the following + * fields are filled in: + * <ul> + * <li>networkId</li> + * <li>SSID</li> + * <li>BSSID</li> + * <li>priority</li> + * <li>allowedProtocols</li> + * <li>allowedKeyManagement</li> + * <li>allowedAuthAlgorithms</li> + * <li>allowedPairwiseCiphers</li> + * <li>allowedGroupCiphers</li> + * </ul> + * @return a list of network configurations in the form of a list + * of {@link WifiConfiguration} objects. + */ + public List<WifiConfiguration> getConfiguredNetworks() { + try { + return mService.getConfiguredNetworks(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Add a new network description to the set of configured networks. + * The {@code networkId} field of the supplied configuration object + * is ignored. + * <p/> + * The new network will be marked DISABLED by default. To enable it, + * called {@link #enableNetwork}. + * + * @param config the set of variables that describe the configuration, + * contained in a {@link WifiConfiguration} object. + * @return the ID of the newly created network description. This is used in + * other operations to specified the network to be acted upon. + * Returns {@code -1} on failure. + */ + public int addNetwork(WifiConfiguration config) { + if (config == null) { + return -1; + } + config.networkId = -1; + return addOrUpdateNetwork(config); + } + + /** + * Update the network description of an existing configured network. + * + * @param config the set of variables that describe the configuration, + * contained in a {@link WifiConfiguration} object. It may + * be sparse, so that only the items that are being changed + * are non-<code>null</code>. The {@code networkId} field + * must be set to the ID of the existing network being updated. + * @return Returns the {@code networkId} of the supplied + * {@code WifiConfiguration} on success. + * <br/> + * Returns {@code -1} on failure, including when the {@code networkId} + * field of the {@code WifiConfiguration} does not refer to an + * existing network. + */ + public int updateNetwork(WifiConfiguration config) { + if (config == null || config.networkId < 0) { + return -1; + } + return addOrUpdateNetwork(config); + } + + /** + * Internal method for doing the RPC that creates a new network description + * or updates an existing one. + * + * @param config The possibly sparse object containing the variables that + * are to set or updated in the network description. + * @return the ID of the network on success, {@code -1} on failure. + */ + private int addOrUpdateNetwork(WifiConfiguration config) { + try { + return mService.addOrUpdateNetwork(config); + } catch (RemoteException e) { + return -1; + } + } + + /** + * Remove the specified network from the list of configured networks. + * This may result in the asynchronous delivery of state change + * events. + * @param netId the integer that identifies the network configuration + * to the supplicant + * @return {@code true} if the operation succeeded + */ + public boolean removeNetwork(int netId) { + try { + return mService.removeNetwork(netId); + } catch (RemoteException e) { + return false; + } + } + + /** + * Allow a previously configured network to be associated with. If + * <code>disableOthers</code> is true, then all other configured + * networks are disabled, and an attempt to connect to the selected + * network is initiated. This may result in the asynchronous delivery + * of state change events. + * @param netId the ID of the network in the list of configured networks + * @param disableOthers if true, disable all other networks. The way to + * select a particular network to connect to is specify {@code true} + * for this parameter. + * @return {@code true} if the operation succeeded + */ + public boolean enableNetwork(int netId, boolean disableOthers) { + try { + return mService.enableNetwork(netId, disableOthers); + } catch (RemoteException e) { + return false; + } + } + + /** + * Disable a configured network. The specified network will not be + * a candidate for associating. This may result in the asynchronous + * delivery of state change events. + * @param netId the ID of the network as returned by {@link #addNetwork}. + * @return {@code true} if the operation succeeded + */ + public boolean disableNetwork(int netId) { + try { + return mService.disableNetwork(netId); + } catch (RemoteException e) { + return false; + } + } + + /** + * 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 + */ + public boolean disconnect() { + try { + return mService.disconnect(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Reconnect to the currently active access point, if we are currently + * disconnected. This may result in the asynchronous delivery of state + * change events. + * @return {@code true} if the operation succeeded + */ + public boolean reconnect() { + try { + return mService.reconnect(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Reconnect to the currently active access point, even if we are already + * connected. This may result in the asynchronous delivery of state + * change events. + * @return {@code true} if the operation succeeded + */ + public boolean reassociate() { + try { + return mService.reassociate(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Check that the supplicant daemon is responding to requests. + * @return {@code true} if we were able to communicate with the supplicant and + * it returned the expected response to the PING message. + */ + public boolean pingSupplicant() { + if (mService == null) + return false; + try { + return mService.pingSupplicant(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Request a scan for access points. Returns immediately. The availability + * of the results is made known later by means of an asynchronous event sent + * on completion of the scan. + * @return {@code true} if the operation succeeded, i.e., the scan was initiated + */ + public boolean startScan() { + try { + return mService.startScan(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Return dynamic information about the current Wi-Fi connection, if any is active. + * @return the Wi-Fi information, contained in {@link WifiInfo}. + */ + public WifiInfo getConnectionInfo() { + try { + return mService.getConnectionInfo(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Return the results of the latest access point scan. + * @return the list of access points found in the most recent scan. + */ + public List<ScanResult> getScanResults() { + try { + return mService.getScanResults(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Tell the supplicant to persist the current list of configured networks. + * <p> + * Note: It is possible for this method to change the network IDs of + * existing networks. You should assume the network IDs can be different + * after calling this method. + * + * @return {@code true} if the operation succeeded + */ + public boolean saveConfiguration() { + try { + return mService.saveConfiguration(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Return the DHCP-assigned addresses from the last successful DHCP request, + * if any. + * @return the DHCP information + */ + public DhcpInfo getDhcpInfo() { + try { + return mService.getDhcpInfo(); + } catch (RemoteException e) { + return null; + } + } + + + /** + * Enable or disable Wi-Fi. + * @param enabled {@code true} to enable, {@code false} to disable. + * @return {@code true} if the operation succeeds (or if the existing state + * is the same as the requested state). + */ + public boolean setWifiEnabled(boolean enabled) { + try { + return mService.setWifiEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the Wi-Fi enabled state. + * @return One of {@link #WIFI_STATE_DISABLED}, + * {@link #WIFI_STATE_DISABLING}, {@link #WIFI_STATE_ENABLED}, + * {@link #WIFI_STATE_ENABLING}, {@link #WIFI_STATE_UNKNOWN} + * @see #isWifiEnabled() + */ + public int getWifiState() { + try { + return mService.getWifiState(); + } catch (RemoteException e) { + return WIFI_STATE_UNKNOWN; + } + } + + /** + * Return whether Wi-Fi is enabled or disabled. + * @return {@code true} if Wi-Fi is enabled + * @see #getWifiState() + */ + public boolean isWifiEnabled() { + return getWifiState() == WIFI_STATE_ENABLED; + } + + /** + * Calculates the level of the signal. This should be used any time a signal + * is being shown. + * + * @param rssi The power of the signal measured in RSSI. + * @param numLevels The number of levels to consider in the calculated + * level. + * @return A level of the signal, given in the range of 0 to numLevels-1 + * (both inclusive). + */ + public static int calculateSignalLevel(int rssi, int numLevels) { + if (rssi <= MIN_RSSI) { + return 0; + } else if (rssi >= MAX_RSSI) { + return numLevels - 1; + } else { + int partitionSize = (MAX_RSSI - MIN_RSSI) / (numLevels - 1); + return (rssi - MIN_RSSI) / partitionSize; + } + } + + /** + * Compares two signal strengths. + * + * @param rssiA The power of the first signal measured in RSSI. + * @param rssiB The power of the second signal measured in RSSI. + * @return Returns <0 if the first signal is weaker than the second signal, + * 0 if the two signals have the same strength, and >0 if the first + * signal is stronger than the second signal. + */ + public static int compareSignalLevel(int rssiA, int rssiB) { + return rssiA - rssiB; + } + + /** + * Allows an application to keep the Wi-Fi radio awake. + * Normally the Wi-Fi radio may turn off when the user has not used the device in a while. + * Acquiring a WifiLock will keep the radio on until the lock is released. Multiple + * applications may hold WifiLocks, and the radio will only be allowed to turn off when no + * WifiLocks are held in any application. + * + * Before using a WifiLock, consider carefully if your application requires Wi-Fi access, or + * could function over a mobile network, if available. A program that needs to download large + * files should hold a WifiLock to ensure that the download will complete, but a program whose + * network usage is occasional or low-bandwidth should not hold a WifiLock to avoid adversely + * affecting battery life. + * + * Note that WifiLocks cannot override the user-level "Wi-Fi Enabled" setting, nor Airplane + * Mode. They simply keep the radio from turning off when Wi-Fi is already on but the device + * is idle. + */ + public class WifiLock { + private String mTag; + private IBinder mBinder; + private int mRefCount; + private boolean mRefCounted; + private boolean mHeld; + + private WifiLock(String tag) { + mTag = tag; + mBinder = new Binder(); + mRefCount = 0; + mRefCounted = true; + mHeld = false; + } + + /** + * Locks the Wi-Fi radio on until {@link #release} is called. + * + * If this WifiLock is reference-counted, each call to {@link #acquire} will increment the + * reference count, and the radio will remain locked as long as the reference count is + * above zero. + * + * If this WifiLock is not reference-counted, the first call to {@link #acquire} will lock + * the radio, but subsequent calls will be ignored. Only one call to {@link #release} + * will be required, regardless of the number of times that {@link #acquire} is called. + */ + public void acquire() { + synchronized (mBinder) { + if (mRefCounted ? (++mRefCount > 0) : (!mHeld)) { + try { + mService.acquireWifiLock(mBinder, mTag); + } catch (RemoteException e) { + } + mHeld = true; + } + } + } + + /** + * Unlocks the Wi-Fi radio, allowing it to turn off when the device is idle. + * + * If this WifiLock is reference-counted, each call to {@link #release} will decrement the + * reference count, and the radio will be unlocked only when the reference count reaches + * zero. If the reference count goes below zero (that is, if {@link #release} is called + * a greater number of times than {@link #acquire}), an exception is thrown. + * + * If this WifiLock is not reference-counted, the first call to {@link #release} (after + * the radio was locked using {@link #acquire}) will unlock the radio, and subsequent + * calls will be ignored. + */ + public void release() { + synchronized (mBinder) { + if (mRefCounted ? (--mRefCount == 0) : (mHeld)) { + try { + mService.releaseWifiLock(mBinder); + } catch (RemoteException e) { + } + mHeld = false; + } + if (mRefCount < 0) { + throw new RuntimeException("WifiLock under-locked " + mTag); + } + } + } + + /** + * Controls whether this is a reference-counted or non-reference-counted WifiLock. + * + * Reference-counted WifiLocks keep track of the number of calls to {@link #acquire} and + * {@link #release}, and only allow the radio to sleep when every call to {@link #acquire} + * has been balanced with a call to {@link #release}. Non-reference-counted WifiLocks + * lock the radio whenever {@link #acquire} is called and it is unlocked, and unlock the + * radio whenever {@link #release} is called and it is locked. + * + * @param refCounted true if this WifiLock should keep a reference count + */ + public void setReferenceCounted(boolean refCounted) { + mRefCounted = refCounted; + } + + /** + * Checks whether this WifiLock is currently held. + * + * @return true if this WifiLock is held, false otherwise + */ + public boolean isHeld() { + synchronized (mBinder) { + return mHeld; + } + } + + public String toString() { + String s1, s2, s3; + synchronized (mBinder) { + s1 = Integer.toHexString(System.identityHashCode(this)); + s2 = mHeld ? "held; " : ""; + if (mRefCounted) { + s3 = "refcounted: refcount = " + mRefCount; + } else { + s3 = "not refcounted"; + } + return "WifiLock{ " + s1 + "; " + s2 + s3 + " }"; + } + } + + @Override + protected void finalize() throws Throwable { + synchronized (mBinder) { + if (mHeld) { + try { + mService.releaseWifiLock(mBinder); + } catch (RemoteException e) { + } + RuntimeInit.crash("WifiLock", new Exception( + "WifiLock finalized while still held: " + mTag)); + } + } + } + } + + /** + * Creates a new WifiLock. + * + * @param tag a tag for the WifiLock to identify it in debugging messages. This string is + * never shown to the user under normal conditions, but should be descriptive + * enough to identify your application and the specific WifiLock within it, if it + * holds multiple WifiLocks. + * + * @return a new, unacquired WifiLock with the given tag. + * + * @see WifiLock + */ + public WifiLock createWifiLock(String tag) { + return new WifiLock(tag); + } + +} diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java new file mode 100644 index 0000000..32d10a1 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2008 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 android.util.Log; +import android.util.Config; +import android.net.NetworkInfo; +import android.net.NetworkStateTracker; + +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +/** + * Listens for events from the wpa_supplicant server, and passes them on + * to the {@link WifiStateTracker} for handling. Runs in its own thread. + * + * {@hide} + */ +public class WifiMonitor { + + private static final String TAG = "WifiMonitor"; + + /** Events we receive from the supplicant daemon */ + + private static final int CONNECTED = 1; + private static final int DISCONNECTED = 2; + private static final int STATE_CHANGE = 3; + private static final int SCAN_RESULTS = 4; + private static final int LINK_SPEED = 5; + private static final int TERMINATING = 6; + private static final int DRIVER_STATE = 7; + private static final int UNKNOWN = 8; + + /** All events coming from the supplicant start with this prefix */ + private static final String eventPrefix = "CTRL-EVENT-"; + private static final int eventPrefixLen = eventPrefix.length(); + + /** All WPA events coming from the supplicant start with this prefix */ + private static final String wpaEventPrefix = "WPA:"; + private static final String passwordKeyMayBeIncorrectEvent = + "pre-shared key may be incorrect"; + + /** + * Names of events from wpa_supplicant (minus the prefix). In the + * format descriptions, * "<code>x</code>" + * designates a dynamic value that needs to be parsed out from the event + * string + */ + /** + * <pre> + * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed + * </pre> + * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point + */ + private static final String connectedEvent = "CONNECTED"; + /** + * <pre> + * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys + * </pre> + */ + private static final String disconnectedEvent = "DISCONNECTED"; + /** + * <pre> + * CTRL-EVENT-STATE-CHANGE x + * </pre> + * <code>x</code> is the numerical value of the new state. + */ + private static final String stateChangeEvent = "STATE-CHANGE"; + /** + * <pre> + * CTRL-EVENT-SCAN-RESULTS ready + * </pre> + */ + private static final String scanResultsEvent = "SCAN-RESULTS"; + + /** + * <pre> + * CTRL-EVENT-LINK-SPEED x Mb/s + * </pre> + * {@code x} is the link speed in Mb/sec. + */ + private static final String linkSpeedEvent = "LINK-SPEED"; + /** + * <pre> + * CTRL-EVENT-TERMINATING - signal x + * </pre> + * <code>x</code> is the signal that caused termination. + */ + private static final String terminatingEvent = "TERMINATING"; + /** + * <pre> + * CTRL-EVENT-DRIVER-STATE state + * </pre> + * <code>state</code> is either STARTED or STOPPED + */ + private static final String driverStateEvent = "DRIVER-STATE"; + + /** + * Regex pattern for extracting an Ethernet-style MAC address from a string. + * Matches a strings like the following:<pre> + * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre> + */ + private static Pattern mConnectedEventPattern = + Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) "); + + private final WifiStateTracker mWifiStateTracker; + + private boolean supplicantConnected; + + private boolean oneShot; + + public WifiMonitor(WifiStateTracker tracker) { + mWifiStateTracker = tracker; + supplicantConnected = false; + oneShot = true; + } + + public void startMonitoring() { + new MonitorThread().start(); + } + + public NetworkStateTracker getNetworkStateTracker() { + return mWifiStateTracker; + } + + class MonitorThread extends Thread { + public MonitorThread() { + super("WifiMonitor"); + } + + public void run() { + + //noinspection InfiniteLoopStatement + for (;;) { + ensureSupplicantConnection(); + + String eventStr = WifiNative.waitForEvent(); + + if (Config.LOGD) Log.v(TAG, "Event [" + eventStr +"]"); + if (eventStr == null) { + continue; + } else if (!eventStr.startsWith(eventPrefix)) { + if (eventStr.startsWith(wpaEventPrefix)) { + if (0 < eventStr.indexOf(passwordKeyMayBeIncorrectEvent)) { + handlePasswordKeyMayBeIncorrect(); + } + } + continue; + } + + String eventName = eventStr.substring(eventPrefixLen); + int nameEnd = eventName.indexOf(' '); + if (nameEnd != -1) + eventName = eventName.substring(0, nameEnd); + if (eventName.length() == 0) { + if (Config.LOGD) Log.i(TAG, "Received wpa_supplicant event with empty event name"); + continue; + } + /* + * Map event name into event enum + */ + int event; + if (eventName.equals(connectedEvent)) + event = CONNECTED; + else if (eventName.equals(disconnectedEvent)) + event = DISCONNECTED; + else if (eventName.equals(stateChangeEvent)) + event = STATE_CHANGE; + else if (eventName.equals(scanResultsEvent)) + event = SCAN_RESULTS; + else if (eventName.equals(linkSpeedEvent)) + event = LINK_SPEED; + else if (eventName.equals(terminatingEvent)) + event = TERMINATING; + else if (eventName.equals(driverStateEvent)) { + event = DRIVER_STATE; + } + else + event = UNKNOWN; + + String eventData = eventStr; + if (event == DRIVER_STATE || event == LINK_SPEED) + eventData = eventData.split(" ")[1]; + else if (event == STATE_CHANGE) { + int ind = eventStr.indexOf(" "); + if (ind != -1) { + eventData = eventStr.substring(ind + 1); + } + } else { + int ind = eventStr.indexOf(" - "); + if (ind != -1) { + eventData = eventStr.substring(ind + 3); + } + } + + if (event == STATE_CHANGE) { + handleSupplicantStateChange(eventData); + } else if (event == DRIVER_STATE) { + handleDriverEvent(eventData); + } else { + handleEvent(event, eventData); + // If supplicant is gone, exit the thread + if (event == TERMINATING) { + break; + } + } + } + } + + private void ensureSupplicantConnection() { + while (!supplicantConnected) { + boolean connected; + synchronized (mWifiStateTracker) { + connected = WifiNative.connectToSupplicant(); + } + if (!connected) { + /* + * If we fail to connect on the very first attempt, send a message + * indicating a lost connection to the supplicant, so that the + * receiver can initialize to the proper state. + */ + if (oneShot) { + oneShot = false; + mWifiStateTracker.notifySupplicantLost(); + } + nap(5); + } else { + supplicantConnected = true; + oneShot = false; + // Send a message indicating that it is now possible to send commands + // to the supplicant + mWifiStateTracker.notifySupplicantConnection(); + + } + } + } + + private void handlePasswordKeyMayBeIncorrect() { + mWifiStateTracker.notifyPasswordKeyMayBeIncorrect(); + } + + private void handleDriverEvent(String state) { + if (state == null) { + return; + } + if (state.equals("STOPPED")) { + mWifiStateTracker.notifyDriverStopped(); + } else if (state.equals("STARTED")) { + mWifiStateTracker.notifyDriverStarted(); + } + } + + /** + * Handle all supplicant events except STATE-CHANGE + * @param event the event type + * @param remainder the rest of the string following the + * event name and " — " + */ + void handleEvent(int event, String remainder) { + switch (event) { + case DISCONNECTED: + handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder); + break; + + case CONNECTED: + handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder); + break; + + case SCAN_RESULTS: + mWifiStateTracker.notifyScanResultsAvailable(); + break; + + case TERMINATING: + supplicantConnected = false; + mWifiStateTracker.notifySupplicantLost(); + break; + + case UNKNOWN: + break; + } + } + + /** + * Handle the supplicant STATE-CHANGE event + * @param dataString New supplicant state string in the format: + * id=network-id state=new-state + */ + private void handleSupplicantStateChange(String dataString) { + String[] dataTokens = dataString.split(" "); + + int networkId = -1; + int newState = -1; + for (String token : dataTokens) { + String[] nameValue = token.split("="); + if (nameValue.length != 2) { + continue; + } + + int value; + try { + value = Integer.parseInt(nameValue[1]); + } catch (NumberFormatException e) { + Log.w(TAG, "STATE-CHANGE non-integer parameter: " + token); + continue; + } + + if (nameValue[0].equals("id")) { + networkId = value; + } else if (nameValue[0].equals("state")) { + newState = value; + } + } + + if (newState == -1) return; + + SupplicantState newSupplicantState = SupplicantState.INVALID; + for (SupplicantState state : SupplicantState.values()) { + if (state.ordinal() == newState) { + newSupplicantState = state; + break; + } + } + if (newSupplicantState == SupplicantState.INVALID) { + Log.w(TAG, "Invalid supplicant state: " + newState); + } + mWifiStateTracker.notifyStateChange(networkId, newSupplicantState); + } + } + + private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) { + String BSSID = null; + int networkId = -1; + if (newState == NetworkInfo.DetailedState.CONNECTED) { + Matcher match = mConnectedEventPattern.matcher(data); + if (!match.find()) { + if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTED event string"); + } else { + BSSID = match.group(1); + try { + networkId = Integer.parseInt(match.group(2)); + } catch (NumberFormatException e) { + networkId = -1; + } + } + } + mWifiStateTracker.notifyStateChange(newState, BSSID, networkId); + } + + /** + * Sleep for a period of time. + * @param secs the number of seconds to sleep + */ + private static void nap(int secs) { + try { + Thread.sleep(secs * 1000); + } catch (InterruptedException e) { + } + } +} diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java new file mode 100644 index 0000000..78bf52e --- /dev/null +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2008 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 android.net.DhcpInfo; + +/** + * 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. + * + * {@hide} + */ +public class WifiNative { + + 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); + + public native static boolean loadDriver(); + + public native static boolean unloadDriver(); + + public native static boolean startSupplicant(); + + public native static boolean stopSupplicant(); + + public native static boolean connectToSupplicant(); + + public native static void closeSupplicantConnection(); + + public native static boolean pingCommand(); + + public native static boolean scanCommand(); + + public native static boolean setScanModeCommand(boolean setActive); + + public native static String listNetworksCommand(); + + public native static int addNetworkCommand(); + + public native static boolean setNetworkVariableCommand(int netId, String name, String value); + + public native static String getNetworkVariableCommand(int netId, String name); + + public native static boolean removeNetworkCommand(int netId); + + public native static boolean enableNetworkCommand(int netId, boolean disableOthers); + + public native static boolean disableNetworkCommand(int netId); + + public native static boolean reconnectCommand(); + + public native static boolean reassociateCommand(); + + public native static boolean disconnectCommand(); + + public native static String statusCommand(); + + public native static int getRssiCommand(); + + public native static int getLinkSpeedCommand(); + + public native static String getMacAddressCommand(); + + public native static String scanResultsCommand(); + + public native static boolean startDriverCommand(); + + public native static boolean stopDriverCommand(); + + public native static boolean setPowerModeCommand(int mode); + + /** + * Sets the bluetooth coexistence mode. + * + * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED}, + * {@link #BLUETOOTH_COEXISTENCE_MODE_ENABLED}, or + * {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}. + * @return Whether the mode was successfully set. + */ + public native static boolean setBluetoothCoexistenceModeCommand(int mode); + + 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 doDhcpRequest(DhcpInfo results); + + public native static String getDhcpError(); + + /** + * 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(); +} diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java new file mode 100644 index 0000000..24cebac --- /dev/null +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -0,0 +1,1710 @@ +/* + * Copyright (C) 2008 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 android.net.NetworkInfo; +import android.net.NetworkStateTracker; +import android.net.DhcpInfo; +import android.net.NetworkUtils; +import android.net.ConnectivityManager; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkInfo.State; +import android.os.Message; +import android.os.Parcelable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.SystemProperties; +import android.os.Looper; +import android.provider.Settings; +import android.provider.Settings.Gservices; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; +import android.util.Config; +import android.app.Notification; +import android.app.PendingIntent; +import android.bluetooth.BluetoothHeadset; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.Context; +import android.database.ContentObserver; + +import java.util.List; +import java.util.ArrayList; +import java.net.UnknownHostException; + +/** + * Track the state of Wifi connectivity. All event handling is done here, + * and all changes in connectivity state are initiated here. + * + * {@hide} + */ +public class WifiStateTracker extends NetworkStateTracker { + + private static final boolean LOCAL_LOGD = Config.LOGD || false; + + private static final String TAG = "WifiStateTracker"; + + // Event log tags (must be in sync with event-log-tags) + private static final int EVENTLOG_NETWORK_STATE_CHANGED = 50021; + private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED = 50022; + private static final int EVENTLOG_DRIVER_STATE_CHANGED = 50023; + private static final int EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED = 50024; + private static final int EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED = 50025; + + // Event codes + private static final int EVENT_SUPPLICANT_CONNECTION = 1; + private static final int EVENT_SUPPLICANT_DISCONNECT = 2; + private static final int EVENT_SUPPLICANT_STATE_CHANGED = 3; + private static final int EVENT_NETWORK_STATE_CHANGED = 4; + private static final int EVENT_SCAN_RESULTS_AVAILABLE = 5; + private static final int EVENT_INTERFACE_CONFIGURATION_SUCCEEDED = 6; + private static final int EVENT_INTERFACE_CONFIGURATION_FAILED = 7; + private static final int EVENT_POLL_INTERVAL = 8; + private static final int EVENT_DHCP_START = 9; + private static final int EVENT_DEFERRED_DISCONNECT = 10; + private static final int EVENT_DEFERRED_RECONNECT = 11; + /** + * The driver is started or stopped. The object will be the state: true for + * started, false for stopped. + */ + private static final int EVENT_DRIVER_STATE_CHANGED = 12; + private static final int EVENT_PASSWORD_KEY_MAY_BE_INCORRECT = 13; + + /** + * Interval in milliseconds between polling for connection + * status items that are not sent via asynchronous events. + * An example is RSSI (signal strength). + */ + private static final int POLL_STATUS_INTERVAL_MSECS = 3000; + + /** + * The max number of the WPA supplicant loop iterations before we + * decide that the loop should be terminated: + */ + private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; + + /** + * When a DISCONNECT event is received, we defer handling it to + * allow for the possibility that the DISCONNECT is about to + * be followed shortly by a CONNECT to the same network we were + * just connected to. In such a case, we don't want to report + * the network as down, nor do we want to reconfigure the network + * interface, etc. If we get a CONNECT event for another network + * within the delay window, we immediately handle the pending + * disconnect before processing the CONNECT.<p/> + * The five second delay is chosen somewhat arbitrarily, but is + * meant to cover most of the cases where a DISCONNECT/CONNECT + * happens to a network. + */ + private static final int DISCONNECT_DELAY_MSECS = 5000; + /** + * When the supplicant goes idle after we do an explicit disconnect + * following a DHCP failure, we need to kick the supplicant into + * trying to associate with access points. + */ + private static final int RECONNECT_DELAY_MSECS = 2000; + + /** + * The maximum number of times we will retry a connection to an access point + * for which we have failed in acquiring an IP address from DHCP. A value of + * N means that we will make N+1 connection attempts in all. + * <p> + * See {@link Gservices#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default + * value if a Gservices value is not present. + */ + private int mMaxReconnectAttempts = 2; + + private static final int DRIVER_POWER_MODE_AUTO = 0; + private static final int DRIVER_POWER_MODE_ACTIVE = 1; + + /** + * The current WPA supplicant loop state (used to detect looping behavior): + */ + private SupplicantState mSupplicantLoopState = SupplicantState.DISCONNECTED; + + /** + * The current number of WPA supplicant loop iterations: + */ + private int mNumSupplicantLoopIterations = 0; + + /** + * True if we received an event that that a password-key may be incorrect. + * If the next incoming supplicant state change event is DISCONNECT, + * broadcast a message that we have a possible password error and disable + * the network. + */ + private boolean mPasswordKeyMayBeIncorrect = false; + + public static final int SUPPL_SCAN_HANDLING_NORMAL = 1; + public static final int SUPPL_SCAN_HANDLING_LIST_ONLY = 2; + + private WifiMonitor mWifiMonitor; + private WifiInfo mWifiInfo; + private List<ScanResult> mScanResults; + private WifiManager mWM; + private boolean mHaveIPAddress; + private boolean mObtainingIPAddress; + private boolean mExplicitlyDisabled; + private boolean mDisconnectPending; + private DhcpHandler mDhcpTarget; + private DhcpInfo mDhcpInfo; + private int mLastSignalLevel = -1; + private String mLastBssid; + private String mLastSsid; + private int mLastNetworkId = -1; + private boolean mUseStaticIp = false; + private int mReconnectCount; + + // Variables relating to the 'available networks' notification + + /** + * The icon to show in the 'available networks' notification. This will also + * be the ID of the Notification given to the NotificationManager. + */ + private static final int ICON_NETWORKS_AVAILABLE = + com.android.internal.R.drawable.stat_notify_wifi_in_range; + /** + * When a notification is shown, we wait this amount before possibly showing it again. + */ + private final long NOTIFICATION_REPEAT_DELAY_MS; + /** + * Whether the user has set the setting to show the 'available networks' notification. + */ + private boolean mNotificationEnabled; + /** + * Observes the user setting to keep {@link #mNotificationEnabled} in sync. + */ + private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; + /** + * The {@link System#currentTimeMillis()} must be at least this value for us + * to show the notification again. + */ + private long mNotificationRepeatTime; + /** + * The Notification object given to the NotificationManager. + */ + private Notification mNotification; + /** + * Whether the notification is being shown, as set by us. That is, if the + * user cancels the notification, we will not receive the callback so this + * will still be true. We only guarantee if this is false, then the + * notification is not showing. + */ + private boolean mNotificationShown; + /** + * The number of continuous scans that must occur before consider the + * supplicant in a scanning state. This allows supplicant to associate with + * remembered networks that are in the scan results. + */ + private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3; + /** + * The number of scans since the last network state change. When this + * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the + * supplicant to actually be scanning. When the network state changes to + * something other than scanning, we reset this to 0. + */ + private int mNumScansSinceNetworkStateChange; + /** + * Observes the static IP address settings. + */ + private StaticIpSettingObserver mStaticIpSettingObserver; + + private boolean mIsScanModeActive; + private boolean mIsScanModeSetDueToAHiddenNetwork; + private boolean mDriverIsStopped; + private String mInterfaceName; + private static String LS = System.getProperty("line.separator"); + + private int[] mSavedConfiguration; + + private Runnable mReleaseWakeLockCallback; + + private static String[] sDnsPropNames; + + private Context mContext; + + /** + * A structure for supplying information about a supplicant state + * change in the STATE_CHANGE event message that comes from the + * WifiMonitor + * thread. + */ + private static class SupplicantStateChangeResult { + SupplicantStateChangeResult(int networkId, SupplicantState state) { + this.state = state; + this.networkId = networkId; + } + int networkId; + SupplicantState state; + } + + /** + * A structure for supplying information about a connection in + * the CONNECTED event message that comes from the WifiMonitor + * thread. + */ + private static class NetworkStateChangeResult { + NetworkStateChangeResult(DetailedState state, String BSSID, int networkId) { + this.state = state; + this.BSSID = BSSID; + this.networkId = networkId; + } + DetailedState state; + String BSSID; + int networkId; + } + + public WifiStateTracker(Context context, Handler target) { + super(context, target, ConnectivityManager.TYPE_WIFI); + + mMaxReconnectAttempts = Gservices.getInt(context.getContentResolver(), + Gservices.WIFI_MAX_DHCP_RETRY_COUNT, mMaxReconnectAttempts); + + mContext = context; + mWifiInfo = new WifiInfo(); + mWifiMonitor = new WifiMonitor(this); + mHaveIPAddress = false; + mObtainingIPAddress = false; + setExplicitlyDisabled(false); + mDisconnectPending = false; + mScanResults = new ArrayList<ScanResult>(); + // Allocate DHCP info object once, and fill it in on each request + mDhcpInfo = new DhcpInfo(); + mIsScanModeSetDueToAHiddenNetwork = false; + + // Setting is in seconds + NOTIFICATION_REPEAT_DELAY_MS = Settings.System.getInt(context.getContentResolver(), + Settings.System.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; + mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler()); + mNotificationEnabledSettingObserver.register(); + + mStaticIpSettingObserver = new StaticIpSettingObserver(new Handler()); + + mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0"); + sDnsPropNames = new String[] { + "dhcp." + mInterfaceName + ".dns1", + "dhcp." + mInterfaceName + ".dns2" + }; + } + + /** + * Helper method: sets the supplicant state and keeps the network + * info updated. + * @param state the new state + */ + private void setSupplicantState(SupplicantState state) { + mWifiInfo.setSupplicantState(state); + updateNetworkInfo(); + } + + public SupplicantState getSupplicantState() { + return mWifiInfo.getSupplicantState(); + } + + /** + * Helper method: sets the supplicant state and keeps the network + * info updated (string version). + * @param stateName the string name of the new state + */ + private void setSupplicantState(String stateName) { + mWifiInfo.setSupplicantState(stateName); + updateNetworkInfo(); + } + + /** + * Helper method: sets the explicitly-disabled state and keeps the + * network info updated. + * @param explicitlyDisabled {@code true} if explicitly disabled. + */ + private void setExplicitlyDisabled(boolean explicitlyDisabled) { + mExplicitlyDisabled = explicitlyDisabled; + updateNetworkInfo(); + } + + /** + * Return the IP addresses of the DNS servers available for the WLAN + * network interface. + * @return a list of DNS addresses, with no holes. + */ + public String[] getNameServers() { + return getNameServerList(sDnsPropNames); + } + + /** + * Return the system properties name associated with the tcp buffer sizes + * for this network. + */ + public String getTcpBufferSizesPropName() { + return "net.tcp.buffersize.wifi"; + } + + public void startMonitoring() { + /* + * Get a handle on the WifiManager. This cannot be done in our + * constructor, because the Wifi service is not yet registered. + */ + mWM = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); + } + + public void startEventLoop() { + mWifiMonitor.startMonitoring(); + } + + /** + * Wi-Fi is considered available as long as we have a connection to the + * supplicant daemon and there is at least one enabled network. + * @return {@code true} if Wi-Fi connections are possible + */ + public boolean isAvailable() { + /* + * TODO: Should we also look at scan results to see whether we're + * in range of any access points? + */ + SupplicantState suppState = mWifiInfo.getSupplicantState(); + return suppState != SupplicantState.UNINITIALIZED && + (suppState != SupplicantState.INACTIVE || mExplicitlyDisabled); + } + + /** + * Helper method: updates the network info object to keep it in sync with + * the Wi-Fi state tracker. + */ + private void updateNetworkInfo() { + mNetworkInfo.setIsAvailable(isAvailable()); + } + + /** + * Report whether the Wi-Fi connection is fully configured for data. + * @return {@code true} if the {@link SupplicantState} is + * {@link android.net.wifi.SupplicantState#COMPLETED COMPLETED}. + */ + public boolean isConnectionCompleted() { + return mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED; + } + + /** + * Send the tracker a notification that a user-entered password key + * may be incorrect (i.e., caused authentication to fail). + */ + void notifyPasswordKeyMayBeIncorrect() { + Message.obtain(this, EVENT_PASSWORD_KEY_MAY_BE_INCORRECT).sendToTarget(); + } + + /** + * Send the tracker a notification that a connection to the supplicant + * daemon has been established. + */ + void notifySupplicantConnection() { + Message.obtain(this, EVENT_SUPPLICANT_CONNECTION).sendToTarget(); + } + + /** + * Send the tracker a notification that the state of the supplicant + * has changed. + * @param newState the new {@code SupplicantState} + */ + void notifyStateChange(int networkId, SupplicantState newState) { + Message msg = Message.obtain( + this, EVENT_SUPPLICANT_STATE_CHANGED, + new SupplicantStateChangeResult(networkId, newState)); + msg.sendToTarget(); + } + + /** + * Send the tracker a notification that the state of Wifi connectivity + * has changed. + * @param newState the new network state + * @param BSSID when the new state is {@link DetailedState#CONNECTED + * NetworkInfo.DetailedState.CONNECTED}, + * this is the MAC address of the access point. Otherwise, it + * is {@code null}. + */ + void notifyStateChange(DetailedState newState, String BSSID, int networkId) { + Message msg = Message.obtain( + this, EVENT_NETWORK_STATE_CHANGED, + new NetworkStateChangeResult(newState, BSSID, networkId)); + msg.sendToTarget(); + } + + /** + * Send the tracker a notification that a scan has completed, and results + * are available. + */ + void notifyScanResultsAvailable() { + // reset the supplicant's handling of scan results to "normal" mode + synchronized (this) { + WifiNative.setScanResultHandlingCommand(SUPPL_SCAN_HANDLING_NORMAL); + } + Message.obtain(this, EVENT_SCAN_RESULTS_AVAILABLE).sendToTarget(); + } + + /** + * Send the tracker a notification that we can no longer communicate with + * the supplicant daemon. + */ + void notifySupplicantLost() { + Message.obtain(this, EVENT_SUPPLICANT_DISCONNECT).sendToTarget(); + } + + /** + * Send the tracker a notification that the Wi-Fi driver has been stopped. + * The notification is in the form of a synthesized DISCONNECTED event. + */ + void notifyDriverStopped() { + setDriverStopped(true); + Message msg = Message.obtain( + this, EVENT_SUPPLICANT_STATE_CHANGED, + new SupplicantStateChangeResult(-1, SupplicantState.DISCONNECTED)); + msg.sendToTarget(); + + // Send a driver stopped message to our handler + Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, Boolean.valueOf(false)) + .sendToTarget(); + } + + /** + * Send the tracker a notification that the Wi-Fi driver has been restarted after + * having been stopped. + */ + void notifyDriverStarted() { + setDriverStopped(false); + + // Send a driver started message to our handler + Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, Boolean.valueOf(true)) + .sendToTarget(); + } + + /** + * Set the interval timer for polling connection information + * that is not delivered asynchronously. + */ + private synchronized void setPollTimer () { + if (!hasMessages(EVENT_POLL_INTERVAL)) { + sendMessageDelayed( + Message.obtain(this, EVENT_POLL_INTERVAL), + POLL_STATUS_INTERVAL_MSECS); + } + } + + private synchronized void setDriverStopped(boolean isStopped) { + mDriverIsStopped = isStopped; + } + + private synchronized boolean isDriverStopped() { + return mDriverIsStopped; + } + + @Override + public void releaseWakeLock() { + if (mReleaseWakeLockCallback != null) { + mReleaseWakeLockCallback.run(); + } + } + + public void setReleaseWakeLockCallback(Runnable callback) { + mReleaseWakeLockCallback = callback; + } + + /** + * Tracks the WPA supplicant states to detect "loop" situations. + * @param newSupplicantState The new WPA supplicant state. + * @return {@code true} if the supplicant loop should be stopped + * and {@code false} if it should continue. + */ + private boolean isSupplicantLooping(SupplicantState newSupplicantState) { + if (SupplicantState.ASSOCIATING.ordinal() <= newSupplicantState.ordinal() + && newSupplicantState.ordinal() < SupplicantState.COMPLETED.ordinal()) { + if (mSupplicantLoopState != newSupplicantState) { + if (newSupplicantState.ordinal() < mSupplicantLoopState.ordinal()) { + ++mNumSupplicantLoopIterations; + } + + mSupplicantLoopState = newSupplicantState; + } + } else if (newSupplicantState == SupplicantState.COMPLETED) { + resetSupplicantLoopState(); + } + + return mNumSupplicantLoopIterations >= MAX_SUPPLICANT_LOOP_ITERATIONS; + } + + /** + * Resets the WPA supplicant loop state. + */ + private void resetSupplicantLoopState() { + mNumSupplicantLoopIterations = 0; + } + + @Override + public void handleMessage(Message msg) { + Intent intent; + + switch (msg.what) { + case EVENT_SUPPLICANT_CONNECTION: + checkUseStaticIp(); + /* + * DHCP requests are blocking, so run them in a separate thread. + */ + HandlerThread dhcpThread = new HandlerThread("DHCP Handler Thread"); + dhcpThread.start(); + mDhcpTarget = new DhcpHandler(dhcpThread.getLooper(), this); + mIsScanModeActive = true; + mLastBssid = null; + mLastSsid = null; + requestConnectionInfo(); + SupplicantState supplState = mWifiInfo.getSupplicantState(); + /** + * The MAC address isn't going to change, so just request it + * once here. + */ + String macaddr; + synchronized (this) { + macaddr = WifiNative.getMacAddressCommand(); + } + if (macaddr != null) { + mWifiInfo.setMacAddress(macaddr); + } + if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant established, state=" + + supplState); + // Wi-Fi supplicant connection state changed: + // [31- 1] Reserved for future use + // [ 0- 0] Connected to supplicant (1) or disconnected from supplicant (0) + EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, 1); + /* + * The COMPLETED state change from the supplicant may have occurred + * in between polling for supplicant availability, in which case + * we didn't perform a DHCP request to get an IP address. + */ + if (supplState == SupplicantState.COMPLETED) { + mLastBssid = mWifiInfo.getBSSID(); + mLastSsid = mWifiInfo.getSSID(); + configureInterface(); + } + intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); + intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, true); + mContext.sendBroadcast(intent); + if (supplState == SupplicantState.COMPLETED && mHaveIPAddress) { + setDetailedState(DetailedState.CONNECTED); + } else { + setDetailedState(WifiInfo.getDetailedStateOf(supplState)); + } + break; + + case EVENT_SUPPLICANT_DISCONNECT: + if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant lost"); + // Wi-Fi supplicant connection state changed: + // [31- 1] Reserved for future use + // [ 0- 0] Connected to supplicant (1) or disconnected from supplicant (0) + EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, 0); + synchronized (this) { + WifiNative.closeSupplicantConnection(); + } + // When supplicant dies, kill the DHCP thread + if (mDhcpTarget != null) { + mDhcpTarget.getLooper().quit(); + mDhcpTarget = null; + } + mContext.removeStickyBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION)); + intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); + intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false); + mContext.sendBroadcast(intent); + setDetailedState(DetailedState.DISCONNECTED); + setSupplicantState(SupplicantState.UNINITIALIZED); + mHaveIPAddress = false; + mObtainingIPAddress = false; + break; + + case EVENT_SUPPLICANT_STATE_CHANGED: + SupplicantStateChangeResult supplicantStateResult = + (SupplicantStateChangeResult) msg.obj; + SupplicantState newState = supplicantStateResult.state; + SupplicantState currentState = mWifiInfo.getSupplicantState(); + + // Wi-Fi supplicant state changed: + // [31- 6] Reserved for future use + // [ 5- 0] Supplicant state ordinal (as defined by SupplicantState) + int eventLogParam = (newState.ordinal() & 0x3f); + EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, eventLogParam); + + if (LOCAL_LOGD) Log.v(TAG, "Changing supplicant state: " + + currentState + + " ==> " + newState); + + int networkId = supplicantStateResult.networkId; + + /* + * Did we get to DISCONNECTED state due to an + * authentication (password) failure? + */ + boolean failedToAuthenticate = false; + if (newState == SupplicantState.DISCONNECTED) { + failedToAuthenticate = mPasswordKeyMayBeIncorrect; + } + mPasswordKeyMayBeIncorrect = false; + + /* + * Keep track of the supplicant state and check if we should + * disable the network + */ + boolean disabledNetwork = false; + if (isSupplicantLooping(newState)) { + if (LOCAL_LOGD) { + Log.v(TAG, + "Stop WPA supplicant loop and disable network"); + } + disabledNetwork = wifiManagerDisableNetwork(networkId); + } + + if (disabledNetwork) { + /* + * Reset the loop state if we disabled the network + */ + resetSupplicantLoopState(); + } else if (newState != currentState || + (newState == SupplicantState.DISCONNECTED && isDriverStopped())) { + setSupplicantState(newState); + if (newState == SupplicantState.DORMANT) { + handleDisconnectedState(DetailedState.FAILED); + sendNetworkStateChangeBroadcast(); + sendEmptyMessageDelayed(EVENT_DEFERRED_RECONNECT, RECONNECT_DELAY_MSECS); + } else if (newState == SupplicantState.DISCONNECTED) { + if (isDriverStopped()) { + handleDisconnectedState(DetailedState.DISCONNECTED); + sendNetworkStateChangeBroadcast(); + } else { + scheduleDisconnect(); + } + } else if (newState != SupplicantState.COMPLETED && !mDisconnectPending) { + /** + * Ignore events that don't change the connectivity state, + * such as WPA rekeying operations. + */ + if (!(currentState == SupplicantState.COMPLETED && + (newState == SupplicantState.ASSOCIATING || + newState == SupplicantState.ASSOCIATED || + newState == SupplicantState.FOUR_WAY_HANDSHAKE || + newState == SupplicantState.GROUP_HANDSHAKE))) { + setDetailedState(WifiInfo.getDetailedStateOf(newState)); + } + } + + intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)newState); + if (failedToAuthenticate) { + wifiManagerDisableNetwork(networkId); + intent.putExtra( + WifiManager.EXTRA_SUPPLICANT_ERROR, + WifiManager.ERROR_AUTHENTICATING); + } + mContext.sendStickyBroadcast(intent); + } + break; + + case EVENT_NETWORK_STATE_CHANGED: + /* + * Each CONNECT or DISCONNECT generates a pair of events. + * One is a supplicant state change event, and the other + * is a network state change event. For connects, the + * supplicant event always arrives first, followed by + * the network state change event. Only the latter event + * has the BSSID, which we are interested in capturing. + * For disconnects, the order is the opposite -- the + * network state change event comes first, followed by + * the supplicant state change event. + */ + NetworkStateChangeResult result = + (NetworkStateChangeResult) msg.obj; + + // Wi-Fi network state changed: + // [31- 6] Reserved for future use + // [ 5- 0] Detailed state ordinal (as defined by NetworkInfo.DetailedState) + eventLogParam = (result.state.ordinal() & 0x3f); + EventLog.writeEvent(EVENTLOG_NETWORK_STATE_CHANGED, eventLogParam); + + if (LOCAL_LOGD) Log.v(TAG, "New network state is " + result.state); + if (result.state != DetailedState.SCANNING) { + /* + * Reset the scan count since there was a network state + * change. This could be from supplicant trying to associate + * with a network. + */ + mNumScansSinceNetworkStateChange = 0; + } + /* + * If the supplicant sent us a CONNECTED event, we don't + * want to send out an indication of overall network + * connectivity until we have our IP address. If the + * supplicant sent us a DISCONNECTED event, we delay + * sending a notification in case a reconnection to + * the same access point occurs within a short time. + */ + if (result.state == DetailedState.DISCONNECTED) { + if (mWifiInfo.getSupplicantState() != SupplicantState.DORMANT) { + scheduleDisconnect(); + } + break; + } + requestConnectionStatus(mWifiInfo); + if (!(result.state == DetailedState.CONNECTED && + (!mHaveIPAddress || mDisconnectPending))) { + setDetailedState(result.state); + } + + if (result.state == DetailedState.CONNECTED) { + /* + * Remove the 'available networks' notification when we + * successfully connect to a network. + */ + setNotificationVisible(false, 0, false, 0); + boolean wasDisconnectPending = mDisconnectPending; + cancelDisconnect(); + if (!TextUtils.equals(mWifiInfo.getSSID(), mLastSsid)) { + /* + * The connection is fully configured as far as link-level + * connectivity is concerned, but we may still need to obtain + * an IP address. But do this only if we are connecting to + * a different access point than we were connected to previously. + */ + if (wasDisconnectPending) { + DetailedState saveState = getNetworkInfo().getDetailedState(); + handleDisconnectedState(DetailedState.DISCONNECTED); + setDetailedStateInternal(saveState); + } + configureInterface(); + } + mLastBssid = result.BSSID; + mLastSsid = mWifiInfo.getSSID(); + mLastNetworkId = result.networkId; + if (mHaveIPAddress) { + setDetailedState(DetailedState.CONNECTED); + } else { + setDetailedState(DetailedState.OBTAINING_IPADDR); + } + } + sendNetworkStateChangeBroadcast(); + break; + + case EVENT_SCAN_RESULTS_AVAILABLE: + + mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + sendScanResultsAvailable(); + /** + * On receiving the first scan results after connecting to + * the supplicant, switch scan mode over to passive. + */ + if (!mIsScanModeSetDueToAHiddenNetwork) { + // This is the only place at the moment where we set + // the scan mode NOT due to a hidden network. This is + // what the second parameter value (false) stands for. + setScanMode(false, false); + } + break; + + case EVENT_POLL_INTERVAL: + if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) { + requestPolledInfo(mWifiInfo); + if (mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED) { + setPollTimer(); + } + } + break; + + case EVENT_DEFERRED_DISCONNECT: + if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) { + handleDisconnectedState(DetailedState.DISCONNECTED); + } + break; + + case EVENT_DEFERRED_RECONNECT: + /* + * If we've exceeded the maximum number of retries for reconnecting + * to a given network, disable the network so that the supplicant + * will try some other network, if any is available. + * TODO: network ID may have changed since we stored it. + */ + if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) { + if (++mReconnectCount > mMaxReconnectAttempts) { + mWM.disableNetwork(mLastNetworkId); + } + WifiNative.reconnectCommand(); + } + break; + + case EVENT_INTERFACE_CONFIGURATION_SUCCEEDED: + /** + * Since this event is sent from another thread, it might have been + * sent after we closed our connection to the supplicant in the course + * of disabling Wi-Fi. In that case, we should just ignore the event. + */ + if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) { + break; + } + mReconnectCount = 0; + mHaveIPAddress = true; + mObtainingIPAddress = false; + mWifiInfo.setIpAddress(mDhcpInfo.ipAddress); + mLastSignalLevel = -1; // force update of signal strength + if (mNetworkInfo.getDetailedState() != DetailedState.CONNECTED) { + setDetailedState(DetailedState.CONNECTED); + intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo); + mContext.sendStickyBroadcast(intent); + } else { + Message.obtain(mTarget, EVENT_CONFIGURATION_CHANGED).sendToTarget(); + } + if (LOCAL_LOGD) Log.v(TAG, "IP configuration: " + mDhcpInfo); + // Wi-Fi interface configuration state changed: + // [31- 1] Reserved for future use + // [ 0- 0] Interface configuration succeeded (1) or failed (0) + EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 1); + + // We've connected successfully, so allow the notification again in the future + resetNotificationTimer(); + break; + + case EVENT_INTERFACE_CONFIGURATION_FAILED: + if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) { + // Wi-Fi interface configuration state changed: + // [31- 1] Reserved for future use + // [ 0- 0] Interface configuration succeeded (1) or failed (0) + EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 0); + + mHaveIPAddress = false; + mWifiInfo.setIpAddress(0); + mObtainingIPAddress = false; + WifiNative.disconnectCommand(); + } + break; + + case EVENT_DRIVER_STATE_CHANGED: + boolean driverStarted = Boolean.TRUE.equals(msg.obj); + + // Wi-Fi driver state changed: + // [31- 1] Reserved for future use + // [ 0- 0] Driver start (1) or stopped (0) + eventLogParam = driverStarted ? 1 : 0; + EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, eventLogParam); + + if (driverStarted) { + synchronized (this) { + // In some situations, supplicant needs to be kickstarted to + // start the background scanning + WifiNative.scanCommand(); + } + } + break; + + case EVENT_PASSWORD_KEY_MAY_BE_INCORRECT: + mPasswordKeyMayBeIncorrect = true; + break; + } + } + + private boolean wifiManagerDisableNetwork(int networkId) { + boolean disabledNetwork = false; + if (0 <= networkId) { + disabledNetwork = mWM.disableNetwork(networkId); + if (LOCAL_LOGD) { + if (disabledNetwork) { + Log.v(TAG, "Disabled network: " + networkId); + } + } + } + if (LOCAL_LOGD) { + if (!disabledNetwork) { + Log.e(TAG, "Failed to disable network:" + + " invalid network id: " + networkId); + } + } + return disabledNetwork; + } + + public synchronized void setScanMode( + boolean isScanModeActive, boolean setDueToAHiddenNetwork) { + mIsScanModeSetDueToAHiddenNetwork = setDueToAHiddenNetwork; + if (mIsScanModeActive != isScanModeActive) { + WifiNative.setScanModeCommand(mIsScanModeActive = isScanModeActive); + } + } + + private void configureInterface() { + setPollTimer(); + mLastSignalLevel = -1; + if (!mUseStaticIp) { + if (!mHaveIPAddress && !mObtainingIPAddress) { + mObtainingIPAddress = true; + mDhcpTarget.obtainMessage(EVENT_DHCP_START).sendToTarget(); + } + } else { + int event; + if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) { + mHaveIPAddress = true; + event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED; + if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration succeeded"); + } else { + mHaveIPAddress = false; + event = EVENT_INTERFACE_CONFIGURATION_FAILED; + if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed"); + } + Message.obtain(this, event).sendToTarget(); + } + } + + /** + * Reset our IP state and send out broadcasts following a disconnect. + * @param newState the {@code DetailedState} to set. Should be either + * {@code DISCONNECTED} or {@code FAILED}. + */ + private void handleDisconnectedState(DetailedState newState) { + if (LOCAL_LOGD) Log.d(TAG, "Deconfiguring interface and stopping DHCP"); + if (mDisconnectPending) { + cancelDisconnect(); + } + Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo); + if (mLastBssid != null) + intent.putExtra(WifiManager.EXTRA_BSSID, mLastBssid); + mWifiInfo.setBSSID(null); + mLastBssid = null; + mLastSsid = null; + resetInterface(); + mContext.sendStickyBroadcast(intent); + setDetailedState(newState); + mDisconnectPending = false; + } + + /** + * Resets the Wi-Fi interface by clearing any state, resetting any sockets + * using the interface, stopping DHCP, and disabling the interface. + */ + public void resetInterface() { + mHaveIPAddress = false; + mObtainingIPAddress = false; + mWifiInfo.setIpAddress(0); + + /* + * Reset connection depends on both the interface and the IP assigned, + * so it should be done before any chance of the IP being lost. + */ + NetworkUtils.resetConnections(mInterfaceName); + + // Stop DHCP + if (mDhcpTarget != null) { + mDhcpTarget.setCancelCallback(true); + } + if (!NetworkUtils.stopDhcp(mInterfaceName)) { + Log.e(TAG, "Could not stop DHCP"); + } + + NetworkUtils.disableInterface(mInterfaceName); + } + + /** + * The supplicant is reporting that we are disconnected from the current + * access point. Often, however, a disconnect will be followed very shortly + * by a reconnect to the same access point. Therefore, we delay resetting + * the connection's IP state for a bit. + */ + private void scheduleDisconnect() { + mDisconnectPending = true; + if (!hasMessages(EVENT_DEFERRED_DISCONNECT)) { + sendEmptyMessageDelayed(EVENT_DEFERRED_DISCONNECT, DISCONNECT_DELAY_MSECS); + } + } + + private void cancelDisconnect() { + mDisconnectPending = false; + removeMessages(EVENT_DEFERRED_DISCONNECT); + } + + public DhcpInfo getDhcpInfo() { + return mDhcpInfo; + } + + public synchronized List<ScanResult> getScanResultsList() { + return mScanResults; + } + + public synchronized void setScanResultsList(List<ScanResult> scanList) { + mScanResults = scanList; + } + + /** + * Get status information for the current connection, if any. + * @return a {@link WifiInfo} object containing information about the current connection + */ + public WifiInfo requestConnectionInfo() { + requestConnectionStatus(mWifiInfo); + requestPolledInfo(mWifiInfo); + + WifiInfo info = mWifiInfo; + if (false) Log.v(TAG, "Status from supplicant: NID=" + info.getNetworkId() + + " SSID=" + (info.getSSID() == null ? "<none>" : info.getSSID()) + + " BSSID=" + (info.getBSSID() == null ? "<none>" : info.getBSSID()) + + " WPA_STATE=" + (info.getSupplicantState() == null ? "<none>" : info.getSupplicantState()) + + " RSSI=" + info.getRssi()); + + return mWifiInfo; + } + + private void requestConnectionStatus(WifiInfo info) { + String reply; + synchronized (this) { + reply = WifiNative.statusCommand(); + } + if (reply == null) { + return; + } + /* + * Parse the reply from the supplicant to the status command, and update + * local state accordingly. The reply is a series of lines of the form + * "name=value". + */ + String SSID = null; + String BSSID = null; + String suppState = null; + int netId = -1; + String[] lines = reply.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("id")) + netId = Integer.parseInt(value); + else if (name.equalsIgnoreCase("ssid")) + SSID = value; + else if (name.equalsIgnoreCase("bssid")) + BSSID = value; + else if (name.equalsIgnoreCase("wpa_state")) + suppState = value; + } + info.setNetworkId(netId); + info.setSSID(SSID); + info.setBSSID(BSSID); + /* + * We only set the supplicant state if the previous state was + * UNINITIALIZED. This should only happen when we first connect to + * the supplicant. Once we're connected, we should always receive + * an event upon any state change, but in this case, we want to + * make sure any listeners are made aware of the state change. + */ + if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED && suppState != null) + setSupplicantState(suppState); + } + + /** + * Get the dynamic information that is not reported via events. + */ + private synchronized void requestPolledInfo(WifiInfo info) + { + int newSignalLevel; + int newRssi = WifiNative.getRssiCommand(); + if (newRssi != -1 && -200 < newRssi && newRssi < 100) { // screen out invalid values + info.setRssi(newRssi); + /** + * Rather then sending the raw RSSI out every time it + * changes, we precalculate the signal level that would + * 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 informing others + * interested in RSSI of all the changes in signal + * level. + */ + // TODO: The "3" below needs to be a symbol somewhere, but + // it's actually the size of an array of icons that's private + // to StatusBar Policy. + newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4); + if (newSignalLevel != mLastSignalLevel) { + sendRssiChangeBroadcast(newRssi); + } + mLastSignalLevel = newSignalLevel; + } else { + info.setRssi(-200); + newSignalLevel = mLastSignalLevel; + } + int newLinkSpeed = WifiNative.getLinkSpeedCommand(); + if (newLinkSpeed != -1) { + info.setLinkSpeed(newLinkSpeed); + } + } + + private void sendRssiChangeBroadcast(final int newRssi) { + Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi); + mContext.sendBroadcast(intent); + } + + private void sendNetworkStateChangeBroadcast() { + Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo); + if (mWifiInfo.getBSSID() != null) + intent.putExtra(WifiManager.EXTRA_BSSID, mWifiInfo.getBSSID()); + mContext.sendStickyBroadcast(intent); + } + + /** + * Disable Wi-Fi connectivity. We do this by going through + * all the configured Wi-Fi networks and disabling them, so + * that the supplicant will not attempt to connect to any + * network. We save the disabled/enabled state of the + * original list, so that + * {@link android.net.NetworkStateTracker#reconnect()} can + * restore it. + */ + public synchronized boolean teardown() { + + // Take down any open network notifications + setNotificationVisible(false, 0, false, 0); + + if (!mNetworkInfo.isConnectedOrConnecting()) + return false; + + List<WifiConfiguration> networks = mWM.getConfiguredNetworks(); + + if (networks == null || networks.size() == 0) { + return false; + } + + /* + * Save the network status in a simple array, whose size is equal + * to the largest network ID in the configuration list. The list + * is always sorted in network ID order, so we only need to look + * at the last element to determine the required size of the + * array. The network IDs are not necessarily contiguous, so the + * saved configuration may have gaps, indicated by placing + * nulls in those positions. + */ + int largest = networks.get(networks.size()-1).networkId; + mSavedConfiguration = new int[largest+1]; + for (int i = 0; i < mSavedConfiguration.length; i++) + mSavedConfiguration[i] = -1; + for (WifiConfiguration conf : networks) { + mSavedConfiguration[conf.networkId] = conf.status; + } + /* + * Now disable the networks. We first disable all but the currently + * connected network. If we disabled that network before disabling + * all the others, the supplicant would try to connect to one of + * the other enabled networks, and we don't want that to happen. + */ + int currentNetwork = -1; + for (WifiConfiguration conf : networks) { + if (conf.status == WifiConfiguration.Status.CURRENT) + currentNetwork = conf.networkId; + else if (conf.status == WifiConfiguration.Status.ENABLED) { + mWM.disableNetwork(conf.networkId); + } + } + if (currentNetwork != -1) + mWM.disableNetwork(currentNetwork); + setExplicitlyDisabled(true); + return true; + } + + /** + * Reenable Wi-Fi connectivity, by iterating through the list of saved + * network statuses, and reenabling each network that had been enabled + * before {@link #teardown()} was called. The network that had been + * active at the time of the teardown() might not be the one chosen + * by the supplicant to connect to after being re-enabled. + */ + public synchronized boolean reconnect() { + /* + * If there's no saved configuration, there must not have been + * a teardown(). Just tell the supplicant to try to reconnect. + */ + setExplicitlyDisabled(false); + if (mSavedConfiguration == null) { + return mWM.reconnect(); + } + + List<WifiConfiguration> networks = mWM.getConfiguredNetworks(); + + if (networks == null || networks.size() == 0) { + return false; + } + + /* + * We know that the IDs of existing networks never change, even when + * networks are added or removed. So there's no danger that we're + * enabling a network different than the one that was disabled in + * teardown(). + */ + for (WifiConfiguration conf : networks) { + if (conf.networkId >= 0 && conf.networkId < mSavedConfiguration.length) { + int status = mSavedConfiguration[conf.networkId]; + if (status != conf.status) { + if (status == WifiConfiguration.Status.ENABLED || status == WifiConfiguration.Status.CURRENT) + mWM.enableNetwork(conf.networkId, false); + else + // this case shouldn't occur + mWM.disableNetwork(conf.networkId); + } + } + } + mSavedConfiguration = null; + return true; + } + + public boolean setRadio(boolean turnOn) { + return mWM.setWifiEnabled(turnOn); + } + + /** + * {@inheritDoc} + * There are currently no Wi-Fi-specific features supported. + * @param feature the name of the feature + * @return {@code -1} indicating failure, always + */ + public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) { + return -1; + } + + /** + * {@inheritDoc} + * There are currently no Wi-Fi-specific features supported. + * @param feature the name of the feature + * @return {@code -1} indicating failure, always + */ + public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) { + return -1; + } + + @Override + public void interpretScanResultsAvailable() { + + // If we shouldn't place a notification on available networks, then + // don't bother doing any of the following + if (!mNotificationEnabled) return; + + NetworkInfo networkInfo = getNetworkInfo(); + + State state = networkInfo.getState(); + if ((state == NetworkInfo.State.DISCONNECTED) + || (state == NetworkInfo.State.UNKNOWN)) { + + // Look for an open network + List<ScanResult> scanResults = getScanResultsList(); + if (scanResults != null) { + int numOpenNetworks = 0; + for (int i = scanResults.size() - 1; i >= 0; i--) { + ScanResult scanResult = scanResults.get(i); + + if (TextUtils.isEmpty(scanResult.capabilities)) { + numOpenNetworks++; + } + } + + if (numOpenNetworks > 0) { + if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) { + /* + * We've scanned continuously at least + * NUM_SCANS_BEFORE_NOTIFICATION times. The user + * probably does not have a remembered network in range, + * since otherwise supplicant would have tried to + * associate and thus resetting this counter. + */ + setNotificationVisible(true, numOpenNetworks, false, 0); + } + return; + } + } + } + + // No open networks in range, remove the notification + setNotificationVisible(false, 0, false, 0); + } + + /** + * {@hide} + */ + public void setNotificationVisible(boolean visible, int numNetworks, boolean force, int delay) { + + // Since we use auto cancel on the notification, when the + // mNetworksAvailableNotificationShown is true, the notification may + // have actually been canceled. However, when it is false we know + // for sure that it is not being shown (it will not be shown any other + // place than here) + + // If it should be hidden and it is already hidden, then noop + if (!visible && !mNotificationShown && !force) { + return; + } + + Message message = null; + if (visible) { + + // Not enough time has passed to show the notification again + if (System.currentTimeMillis() < mNotificationRepeatTime) { + return; + } + + if (mNotification == null) { + // Cache the Notification mainly so we can remove the + // EVENT_NOTIFICATION_CHANGED message with this Notification from + // the queue later + mNotification = new Notification(); + mNotification.when = 0; + mNotification.icon = ICON_NETWORKS_AVAILABLE; + mNotification.flags = Notification.FLAG_AUTO_CANCEL; + mNotification.contentIntent = PendingIntent.getActivity(mContext, 0, + new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0); + } + + CharSequence title = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available, numNetworks); + CharSequence details = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available_detailed, numNetworks); + mNotification.tickerText = title; + mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent); + + mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS; + + message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1, + ICON_NETWORKS_AVAILABLE, mNotification); + + } else { + + // Remove any pending messages to show the notification + mTarget.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification); + + message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0, ICON_NETWORKS_AVAILABLE); + } + + mTarget.sendMessageDelayed(message, delay); + + mNotificationShown = visible; + } + + /** + * Clears variables related to tracking whether a notification has been + * shown recently. + * <p> + * After calling this method, the timer that prevents notifications from + * being shown too often will be cleared. + */ + private void resetNotificationTimer() { + mNotificationRepeatTime = 0; + mNumScansSinceNetworkStateChange = 0; + } + + public synchronized boolean addToBlacklist(String bssid) { + return WifiNative.addToBlacklistCommand(bssid); + } + + public synchronized boolean clearBlacklist() { + return WifiNative.clearBlacklistCommand(); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(mWifiInfo).append(LS); + sb.append("interface ").append(mInterfaceName).append(LS); + sb.append(mDhcpInfo).append(LS); + sb.append("haveIpAddress=").append(mHaveIPAddress). + append(", obtainingIpAddress=").append(mObtainingIPAddress). + append(", scanModeActive=").append(mIsScanModeActive).append(LS). + append("lastSignalLevel=").append(mLastSignalLevel). + append(", explicitlyDisabled=").append(mExplicitlyDisabled); + if (mSavedConfiguration != null) { + sb.append(LS); + sb.append("Saved network configurations:").append(LS); + for (int i = 0; i < mSavedConfiguration.length; i++) { + int cs = mSavedConfiguration[i]; + sb.append(" ").append(i).append(": ").append(cs).append(LS); + } + } + return sb.toString(); + } + + private class DhcpHandler extends Handler { + + private Handler mTarget; + + /** + * Whether to skip the DHCP result callback to the target. For example, + * this could be set if the network we were requesting an IP for has + * since been disconnected. + * <p> + * Note: There is still a chance where the client's intended DHCP + * request not being canceled. For example, we are request for IP on + * A, and he queues request for IP on B, and then cancels the request on + * B while we're still requesting from A. + */ + private boolean mCancelCallback; + + /** + * Instance of the bluetooth headset helper. This needs to be created + * early because there is a delay before it actually 'connects', as + * noted by its javadoc. If we check before it is connected, it will be + * in an error state and we will not disable coexistence. + */ + private BluetoothHeadset mBluetoothHeadset; + + public DhcpHandler(Looper looper, Handler target) { + super(looper); + mTarget = target; + + mBluetoothHeadset = new BluetoothHeadset(mContext); + } + + public void handleMessage(Message msg) { + int event; + + switch (msg.what) { + case EVENT_DHCP_START: + + boolean modifiedBluetoothCoexistenceMode = false; + if (shouldDisableCoexistenceMode()) { + /* + * There are problems setting the Wi-Fi driver's power + * mode to active when bluetooth coexistence mode is + * enabled or sense. + * <p> + * We set Wi-Fi to active mode when + * obtaining an IP address because we've found + * compatibility issues with some routers with low power + * mode. + * <p> + * In order for this active power mode to properly be set, + * we disable coexistence mode until we're done with + * obtaining an IP address. One exception is if we + * are currently connected to a headset, since disabling + * coexistence would interrupt that connection. + */ + modifiedBluetoothCoexistenceMode = true; + + // Disable the coexistence mode + synchronized (WifiStateTracker.this) { + WifiNative.setBluetoothCoexistenceModeCommand( + WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED); + } + } + + synchronized (WifiStateTracker.this) { + WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE); + } + synchronized (this) { + // A new request is being made, so assume we will callback + mCancelCallback = false; + } + Log.d(TAG, "DhcpHandler: DHCP request started"); + if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) { + event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED; + if (LOCAL_LOGD) Log.v(TAG, "DhcpHandler: DHCP request succeeded"); + } else { + event = EVENT_INTERFACE_CONFIGURATION_FAILED; + Log.i(TAG, "DhcpHandler: DHCP request failed: " + + NetworkUtils.getDhcpError()); + } + synchronized (WifiStateTracker.this) { + WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_AUTO); + } + + if (modifiedBluetoothCoexistenceMode) { + // Set the coexistence mode back to its default value + synchronized (WifiStateTracker.this) { + WifiNative.setBluetoothCoexistenceModeCommand( + WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE); + } + } + + synchronized (this) { + if (!mCancelCallback) { + Message.obtain(mTarget, event).sendToTarget(); + } + } + break; + } + } + + public synchronized void setCancelCallback(boolean cancelCallback) { + mCancelCallback = cancelCallback; + } + + /** + * Whether to disable coexistence mode while obtaining IP address. This + * logic will return true only if the current bluetooth + * headset/handsfree state is disconnected. This means if it is in an + * error state, we will NOT disable coexistence mode to err on the side + * of safety. + * + * @return Whether to disable coexistence mode. + */ + private boolean shouldDisableCoexistenceMode() { + int state = mBluetoothHeadset.getState(); + return state == BluetoothHeadset.STATE_DISCONNECTED; + } + } + + private void checkUseStaticIp() { + mUseStaticIp = false; + final ContentResolver cr = mContext.getContentResolver(); + try { + if (Settings.System.getInt(cr, Settings.System.WIFI_USE_STATIC_IP) == 0) { + return; + } + } catch (Settings.SettingNotFoundException e) { + return; + } + + try { + String addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_IP); + if (addr != null) { + mDhcpInfo.ipAddress = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_GATEWAY); + if (addr != null) { + mDhcpInfo.gateway = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_NETMASK); + if (addr != null) { + mDhcpInfo.netmask = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS1); + if (addr != null) { + mDhcpInfo.dns1 = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS2); + if (addr != null) { + mDhcpInfo.dns2 = stringToIpAddr(addr); + } else { + mDhcpInfo.dns2 = 0; + } + } catch (UnknownHostException e) { + return; + } + mUseStaticIp = true; + } + + private static int stringToIpAddr(String addrString) throws UnknownHostException { + try { + String[] parts = addrString.split("\\."); + if (parts.length != 4) { + throw new UnknownHostException(addrString); + } + + int a = Integer.parseInt(parts[0]) ; + int b = Integer.parseInt(parts[1]) << 8; + int c = Integer.parseInt(parts[2]) << 16; + int d = Integer.parseInt(parts[3]) << 24; + + return a | b | c | d; + } catch (NumberFormatException ex) { + throw new UnknownHostException(addrString); + } + } + + private class StaticIpSettingObserver extends ContentObserver { + public StaticIpSettingObserver(Handler handler) { + super(handler); + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_USE_STATIC_IP), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_IP), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_GATEWAY), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_NETMASK), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_DNS1), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_DNS2), false, this); + } + + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) { + return; + } + + boolean wasStaticIp = mUseStaticIp; + int oIp, oGw, oMsk, oDns1, oDns2; + oIp = oGw = oMsk = oDns1 = oDns2 = 0; + if (wasStaticIp) { + oIp = mDhcpInfo.ipAddress; + oGw = mDhcpInfo.gateway; + oMsk = mDhcpInfo.netmask; + oDns1 = mDhcpInfo.dns1; + oDns2 = mDhcpInfo.dns2; + } + checkUseStaticIp(); + boolean changed = + (wasStaticIp != mUseStaticIp) || + (wasStaticIp && ( + oIp != mDhcpInfo.ipAddress || + oGw != mDhcpInfo.gateway || + oMsk != mDhcpInfo.netmask || + oDns1 != mDhcpInfo.dns1 || + oDns2 != mDhcpInfo.dns2)); + + if (changed) { + resetInterface(); + configureInterface(); + if (mUseStaticIp) { + mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED).sendToTarget(); + } + } + } + } + + private class NotificationEnabledSettingObserver extends ContentObserver { + + public NotificationEnabledSettingObserver(Handler handler) { + super(handler); + } + + public void register() { + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(Settings.System.getUriFor(Settings.System.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); + mNotificationEnabled = getValue(); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + mNotificationEnabled = getValue(); + if (!mNotificationEnabled) { + // Remove any notification that may be showing + setNotificationVisible(false, 0, true, 0); + } + + resetNotificationTimer(); + } + + private boolean getValue() { + return Settings.System.getInt(mContext.getContentResolver(), + Settings.System.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; + } + } +} diff --git a/wifi/java/android/net/wifi/package.html b/wifi/java/android/net/wifi/package.html new file mode 100644 index 0000000..530313d --- /dev/null +++ b/wifi/java/android/net/wifi/package.html @@ -0,0 +1,12 @@ +<HTML> +<BODY> +Provides classes to manage Wi-Fi functionality on the device. +<p>The Wi-Fi APIs provide a means by which applications can communicate +with the lower-level wireless stack that provides Wi-Fi network access. Almost all +information from the device supplicant is available, including the connected network's +link speed, IP address, negotiation state, and more, plus information about other +networks that are available. Some other API features include the ability to +scan, add, save, terminate and initiate Wi-Fi connections.</p> +<p>Remember, not all Android devices are guaranteed to have Wi-Fi functionality.</p> +</BODY> +</HTML> |