blob: cc47d08b00a2a5ae1f132cebd3300597cd1e7419 [file] [log] [blame]
/*
* 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.app.ActivityManagerNative;
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.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
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.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothA2dp;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.Context;
import android.database.ContentObserver;
import com.android.internal.app.IBatteryStats;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
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;
private static final int EVENT_MAYBE_START_SCAN_POST_DISCONNECT = 14;
/**
* The driver state indication.
*/
private static final int DRIVER_STARTED = 0;
private static final int DRIVER_STOPPED = 1;
private static final int DRIVER_HUNG = 2;
/**
* 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;
/**
* When the supplicant disconnects from an AP it sometimes forgets
* to restart scanning. Wait this delay before asking it to start
* scanning (in case it forgot). 15 sec is the standard delay between
* scans.
*/
private static final int KICKSTART_SCANNING_DELAY_MSECS = 15000;
/**
* 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 Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
* value if a Settings value is not present.
*/
private static final int DEFAULT_MAX_DHCP_RETRIES = 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;
/**
* The current number of supplicant state changes. This is used to determine
* if we've received any new info since we found out it was DISCONNECTED or
* INACTIVE. If we haven't for X ms, we then request a scan - it should have
* done that automatically, but sometimes some firmware does not.
*/
private int mNumSupplicantStateChanges = 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 mTornDownByConnMgr;
/**
* A DISCONNECT event has been received, but processing it
* is being deferred.
*/
private boolean mDisconnectPending;
/**
* An operation has been performed as a result of which we expect the next event
* will be a DISCONNECT.
*/
private boolean mDisconnectExpected;
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;
// used to store the (non-persisted) num determined during device boot
// (from mcc or other phone info) before the driver is started.
private int mNumAllowedChannels = 0;
// 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 SettingsObserver mSettingsObserver;
private boolean mIsScanModeActive;
private boolean mEnableRssiPolling;
// Wi-Fi run states:
private static final int RUN_STATE_STARTING = 1;
private static final int RUN_STATE_RUNNING = 2;
private static final int RUN_STATE_STOPPING = 3;
private static final int RUN_STATE_STOPPED = 4;
private static final String mRunStateNames[] = {
"Starting",
"Running",
"Stopping",
"Stopped"
};
private int mRunState;
private final IBatteryStats mBatteryStats;
private boolean mIsScanOnly;
private BluetoothA2dp mBluetoothA2dp;
private String mInterfaceName;
private static String LS = System.getProperty("line.separator");
private Runnable mReleaseWakeLockCallback;
private static String[] sDnsPropNames;
/**
* 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, String BSSID, SupplicantState state) {
this.state = state;
this.BSSID = BSSID;
this.networkId = networkId;
}
int networkId;
String BSSID;
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, 0, "WIFI", "");
mWifiInfo = new WifiInfo();
mWifiMonitor = new WifiMonitor(this);
mHaveIpAddress = false;
mObtainingIpAddress = false;
setTornDownByConnMgr(false);
mDisconnectPending = false;
mScanResults = new ArrayList<ScanResult>();
// Allocate DHCP info object once, and fill it in on each request
mDhcpInfo = new DhcpInfo();
mRunState = RUN_STATE_STARTING;
// Setting is in seconds
NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
mNotificationEnabledSettingObserver.register();
mSettingsObserver = new SettingsObserver(new Handler());
mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
sDnsPropNames = new String[] {
"dhcp." + mInterfaceName + ".dns1",
"dhcp." + mInterfaceName + ".dns2"
};
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
}
/**
* 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();
checkPollTimer();
}
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();
checkPollTimer();
}
/**
* Helper method: sets the boolean indicating that the connection
* manager asked the network to be torn down (and so only the connection
* manager can set it up again).
* network info updated.
* @param flag {@code true} if explicitly disabled.
*/
private void setTornDownByConnMgr(boolean flag) {
mTornDownByConnMgr = flag;
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 name of our WLAN network interface.
* @return the name of our interface.
*/
public String getInterfaceName() {
return mInterfaceName;
}
/**
* 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. If a teardown
* was explicitly requested, then Wi-Fi can be restarted with a reconnect
* request, so it is considered available. If the driver has been stopped
* for any reason other than a teardown request, Wi-Fi is considered
* unavailable.
* @return {@code true} if Wi-Fi connections are possible
*/
public synchronized boolean isAvailable() {
/*
* TODO: Need to also look at scan results to see whether we're
* in range of any access points. If we have scan results that
* are no more than N seconds old, use those, otherwise, initiate
* a scan and wait for the results. This only matters if we
* allow mobile to be the preferred network.
*/
SupplicantState suppState = mWifiInfo.getSupplicantState();
return suppState != SupplicantState.UNINITIALIZED &&
suppState != SupplicantState.INACTIVE &&
(mTornDownByConnMgr || !isDriverStopped());
}
/**
* {@inheritDoc}
* There are currently no defined Wi-Fi subtypes.
*/
public int getNetworkSubtype() {
return 0;
}
/**
* 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;
}
/**
* Report whether the Wi-Fi connection has successfully acquired an IP address.
* @return {@code true} if the Wi-Fi connection has been assigned an IP address.
*/
public boolean hasIpAddress() {
return mHaveIpAddress;
}
/**
* Send the tracker a notification that a user-entered password key
* may be incorrect (i.e., caused authentication to fail).
*/
void notifyPasswordKeyMayBeIncorrect() {
sendEmptyMessage(EVENT_PASSWORD_KEY_MAY_BE_INCORRECT);
}
/**
* Send the tracker a notification that a connection to the supplicant
* daemon has been established.
*/
void notifySupplicantConnection() {
sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);
}
/**
* Send the tracker a notification that the state of the supplicant
* has changed.
* @param networkId the configured network on which the state change occurred
* @param newState the new {@code SupplicantState}
*/
void notifyStateChange(int networkId, String BSSID, SupplicantState newState) {
Message msg = Message.obtain(
this, EVENT_SUPPLICANT_STATE_CHANGED,
new SupplicantStateChangeResult(networkId, BSSID, newState));
msg.sendToTarget();
}
/**
* Send the tracker a notification that the state of Wifi connectivity
* has changed.
* @param networkId the configured network on which the state change occurred
* @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
setScanResultHandling(SUPPL_SCAN_HANDLING_NORMAL);
sendEmptyMessage(EVENT_SCAN_RESULTS_AVAILABLE);
}
/**
* Send the tracker a notification that we can no longer communicate with
* the supplicant daemon.
*/
void notifySupplicantLost() {
sendEmptyMessage(EVENT_SUPPLICANT_DISCONNECT);
}
/**
* Send the tracker a notification that the Wi-Fi driver has been stopped.
*/
void notifyDriverStopped() {
mRunState = RUN_STATE_STOPPED;
// Send a driver stopped message to our handler
Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STOPPED, 0).sendToTarget();
}
/**
* Send the tracker a notification that the Wi-Fi driver has been restarted after
* having been stopped.
*/
void notifyDriverStarted() {
// Send a driver started message to our handler
Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STARTED, 0).sendToTarget();
}
/**
* Send the tracker a notification that the Wi-Fi driver has hung and needs restarting.
*/
void notifyDriverHung() {
// Send a driver hanged message to our handler
Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_HUNG, 0).sendToTarget();
}
/**
* Set the interval timer for polling connection information
* that is not delivered asynchronously.
*/
private synchronized void checkPollTimer() {
if (mEnableRssiPolling &&
mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED &&
!hasMessages(EVENT_POLL_INTERVAL)) {
sendEmptyMessageDelayed(EVENT_POLL_INTERVAL, POLL_STATUS_INTERVAL_MSECS);
}
}
private synchronized boolean isDriverStopped() {
return mRunState == RUN_STATE_STOPPED || mRunState == RUN_STATE_STOPPING;
}
private void noteRunState() {
try {
if (mRunState == RUN_STATE_RUNNING) {
mBatteryStats.noteWifiRunning();
} else if (mRunState == RUN_STATE_STOPPED) {
mBatteryStats.noteWifiStopped();
}
} catch (RemoteException ignore) {
}
}
/**
* Set the run state to either "normal" or "scan-only".
* @param scanOnlyMode true if the new mode should be scan-only.
*/
public synchronized void setScanOnlyMode(boolean scanOnlyMode) {
// do nothing unless scan-only mode is changing
if (mIsScanOnly != scanOnlyMode) {
int scanType = (scanOnlyMode ?
SUPPL_SCAN_HANDLING_LIST_ONLY : SUPPL_SCAN_HANDLING_NORMAL);
if (LOCAL_LOGD) Log.v(TAG, "Scan-only mode changing to " + scanOnlyMode + " scanType=" + scanType);
if (setScanResultHandling(scanType)) {
mIsScanOnly = scanOnlyMode;
if (!isDriverStopped()) {
if (scanOnlyMode) {
disconnect();
} else {
reconnectCommand();
}
}
}
}
}
private void checkIsBluetoothPlaying() {
boolean isBluetoothPlaying = false;
Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks();
for (BluetoothDevice device : connected) {
if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) {
isBluetoothPlaying = true;
break;
}
}
setBluetoothScanMode(isBluetoothPlaying);
}
public void enableRssiPolling(boolean enable) {
if (mEnableRssiPolling != enable) {
mEnableRssiPolling = enable;
checkPollTimer();
}
}
@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:
mRunState = RUN_STATE_RUNNING;
noteRunState();
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;
mTornDownByConnMgr = false;
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 = getMacAddress();
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- 2] Reserved for future use
// [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
// or supplicant died (2)
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();
}
if (ActivityManagerNative.isSystemReady()) {
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));
}
/*
* Filter out multicast packets. This saves battery power, since
* the CPU doesn't have to spend time processing packets that
* are going to end up being thrown away.
*/
mWM.initializeMulticastFiltering();
if (mBluetoothA2dp == null) {
mBluetoothA2dp = new BluetoothA2dp(mContext);
}
checkIsBluetoothPlaying();
// initialize this after the supplicant is alive
setNumAllowedChannels();
break;
case EVENT_SUPPLICANT_DISCONNECT:
mRunState = RUN_STATE_STOPPED;
noteRunState();
int wifiState = mWM.getWifiState();
boolean died = wifiState != WifiManager.WIFI_STATE_DISABLED &&
wifiState != WifiManager.WIFI_STATE_DISABLING;
if (died) {
if (LOCAL_LOGD) Log.v(TAG, "Supplicant died unexpectedly");
} else {
if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant lost");
}
// Wi-Fi supplicant connection state changed:
// [31- 2] Reserved for future use
// [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
// or supplicant died (2)
EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, died ? 2 : 0);
closeSupplicantConnection();
if (died) {
resetConnections(true);
}
// When supplicant dies, kill the DHCP thread
if (mDhcpTarget != null) {
mDhcpTarget.getLooper().quit();
mDhcpTarget = null;
}
mContext.removeStickyBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
if (ActivityManagerNative.isSystemReady()) {
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;
if (died) {
mWM.setWifiEnabled(false);
}
break;
case EVENT_MAYBE_START_SCAN_POST_DISCONNECT:
// Only do this if we haven't gotten a new supplicant status since the timer
// started
if (mNumSupplicantStateChanges == msg.arg1) {
scan(false); // do a passive scan
}
break;
case EVENT_SUPPLICANT_STATE_CHANGED:
mNumSupplicantStateChanges++;
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;
/**
* The SupplicantState BSSID value is valid in ASSOCIATING state only.
* The NetworkState BSSID value comes upon a successful connection.
*/
if (supplicantStateResult.state == SupplicantState.ASSOCIATING) {
mLastBssid = supplicantStateResult.BSSID;
}
/*
* If we get disconnect or inactive we need to start our
* watchdog timer to start a scan
*/
if (newState == SupplicantState.DISCONNECTED ||
newState == SupplicantState.INACTIVE) {
sendMessageDelayed(obtainMessage(EVENT_MAYBE_START_SCAN_POST_DISCONNECT,
mNumSupplicantStateChanges, 0), KICKSTART_SCANNING_DELAY_MSECS);
}
/*
* 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) {
DetailedState newDetailedState;
Message reconnectMsg = obtainMessage(EVENT_DEFERRED_RECONNECT, mLastBssid);
if (mIsScanOnly || mRunState == RUN_STATE_STOPPING) {
newDetailedState = DetailedState.IDLE;
} else {
newDetailedState = DetailedState.FAILED;
}
handleDisconnectedState(newDetailedState, true);
/**
* If we were associated with a network (networkId != -1),
* assume we reached this state because of a failed attempt
* to acquire an IP address, and attempt another connection
* and IP address acquisition in RECONNECT_DELAY_MSECS
* milliseconds.
*/
if (mRunState == RUN_STATE_RUNNING && !mIsScanOnly && networkId != -1) {
sendMessageDelayed(reconnectMsg, RECONNECT_DELAY_MSECS);
} else if (mRunState == RUN_STATE_STOPPING) {
stopDriver();
} else if (mRunState == RUN_STATE_STARTING && !mIsScanOnly) {
reconnectCommand();
}
} else if (newState == SupplicantState.DISCONNECTED) {
mHaveIpAddress = false;
if (isDriverStopped() || mDisconnectExpected) {
handleDisconnectedState(DetailedState.DISCONNECTED, true);
} 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));
}
}
mDisconnectExpected = false;
intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)newState);
if (failedToAuthenticate) {
if (LOCAL_LOGD) Log.d(TAG, "Failed to authenticate, disabling network " + networkId);
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 we're in scan-only mode, don't advance the state machine, and
* don't report the state change to clients.
*/
if (mIsScanOnly) {
if (LOCAL_LOGD) Log.v(TAG, "Dropping event in scan-only mode");
break;
}
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();
/*
* The connection is fully configured as far as link-level
* connectivity is concerned, but we may still need to obtain
* an IP address.
*/
if (wasDisconnectPending) {
DetailedState saveState = getNetworkInfo().getDetailedState();
handleDisconnectedState(DetailedState.DISCONNECTED, false);
setDetailedStateInternal(saveState);
}
configureInterface();
mLastBssid = result.BSSID;
mLastSsid = mWifiInfo.getSSID();
mLastNetworkId = result.networkId;
if (mHaveIpAddress) {
setDetailedState(DetailedState.CONNECTED);
} else {
setDetailedState(DetailedState.OBTAINING_IPADDR);
}
}
sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
break;
case EVENT_SCAN_RESULTS_AVAILABLE:
if (ActivityManagerNative.isSystemReady()) {
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.
*/
setScanMode(false);
break;
case EVENT_POLL_INTERVAL:
if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
requestPolledInfo(mWifiInfo, true);
checkPollTimer();
}
break;
case EVENT_DEFERRED_DISCONNECT:
if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
handleDisconnectedState(DetailedState.DISCONNECTED, true);
}
break;
case EVENT_DEFERRED_RECONNECT:
String BSSID = msg.obj.toString();
/**
* If we've exceeded the maximum number of retries for reconnecting
* to a given network, blacklist the BSSID to allow a connection attempt on
* an alternate BSSID if available
*/
if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
if (++mReconnectCount > getMaxDhcpRetries()) {
if (LOCAL_LOGD) {
Log.d(TAG, "Failed reconnect count: " +
mReconnectCount + " Blacklisting " + BSSID);
}
addToBlacklist(BSSID);
}
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);
sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
} else {
mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
}
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;
disconnect();
}
break;
case EVENT_DRIVER_STATE_CHANGED:
// Wi-Fi driver state changed:
// 0 STARTED
// 1 STOPPED
// 2 HUNG
EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, msg.arg1);
switch (msg.arg1) {
case DRIVER_STARTED:
/**
* Set the number of allowed radio channels according
* to the system setting, since it gets reset by the
* driver upon changing to the STARTED state.
*/
setNumAllowedChannels();
synchronized (this) {
if (mRunState == RUN_STATE_STARTING) {
mRunState = RUN_STATE_RUNNING;
if (!mIsScanOnly) {
reconnectCommand();
} else {
// In some situations, supplicant needs to be kickstarted to
// start the background scanning
scan(true);
}
}
}
break;
case DRIVER_HUNG:
Log.e(TAG, "Wifi Driver reports HUNG - reloading.");
/**
* restart the driver - toggle off and on
*/
mWM.setWifiEnabled(false);
mWM.setWifiEnabled(true);
break;
}
noteRunState();
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;
}
private void configureInterface() {
checkPollTimer();
mLastSignalLevel = -1;
if (!mUseStaticIp) {
if (!mHaveIpAddress && !mObtainingIpAddress) {
mObtainingIpAddress = true;
mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START);
}
} 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");
}
sendEmptyMessage(event);
}
}
/**
* 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}.
* @param disableInterface indicates whether the interface should
* be disabled
*/
private void handleDisconnectedState(DetailedState newState, boolean disableInterface) {
if (mDisconnectPending) {
cancelDisconnect();
}
mDisconnectExpected = false;
resetConnections(disableInterface);
setDetailedState(newState);
sendNetworkStateChangeBroadcast(mLastBssid);
mWifiInfo.setBSSID(null);
mLastBssid = null;
mLastSsid = null;
mDisconnectPending = false;
}
/**
* Resets the Wi-Fi Connections by clearing any state, resetting any sockets
* using the interface, stopping DHCP, and disabling the interface.
*/
public void resetConnections(boolean disableInterface) {
if (LOCAL_LOGD) Log.d(TAG, "Reset connections and stopping DHCP");
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);
mDhcpTarget.removeMessages(EVENT_DHCP_START);
}
if (!NetworkUtils.stopDhcp(mInterfaceName)) {
Log.e(TAG, "Could not stop DHCP");
}
/**
* Interface is re-enabled in the supplicant
* when moving out of ASSOCIATING state
*/
if(disableInterface) {
if (LOCAL_LOGD) Log.d(TAG, "Disabling interface");
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, false);
return mWifiInfo;
}
private void requestConnectionStatus(WifiInfo info) {
String reply = status();
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.
* @param info the object into which the information should be captured.
*/
private synchronized void requestPolledInfo(WifiInfo info, boolean polling)
{
int newRssi = (polling ? getRssiApprox() : getRssi());
if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values
/* some implementations avoid negative values by adding 256
* so we need to adjust for that here.
*/
if (newRssi > 0) newRssi -= 256;
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 second arg to the call below needs to be a symbol somewhere, but
// it's actually the size of an array of icons that's private
// to StatusBar Policy.
int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4);
if (newSignalLevel != mLastSignalLevel) {
sendRssiChangeBroadcast(newRssi);
}
mLastSignalLevel = newSignalLevel;
} else {
info.setRssi(-200);
}
int newLinkSpeed = getLinkSpeed();
if (newLinkSpeed != -1) {
info.setLinkSpeed(newLinkSpeed);
}
}
private void sendRssiChangeBroadcast(final int newRssi) {
if (ActivityManagerNative.isSystemReady()) {
Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
mContext.sendBroadcast(intent);
}
}
private void sendNetworkStateChangeBroadcast(String bssid) {
Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
if (bssid != null)
intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
mContext.sendStickyBroadcast(intent);
}
/**
* Disable Wi-Fi connectivity by stopping the driver.
*/
public boolean teardown() {
if (!mTornDownByConnMgr) {
if (disconnectAndStop()) {
setTornDownByConnMgr(true);
return true;
} else {
return false;
}
} else {
return true;
}
}
/**
* Reenable Wi-Fi connectivity by restarting the driver.
*/
public boolean reconnect() {
if (mTornDownByConnMgr) {
if (restart()) {
setTornDownByConnMgr(false);
return true;
} else {
return false;
}
} else {
return true;
}
}
/**
* We want to stop the driver, but if we're connected to a network,
* we first want to disconnect, so that the supplicant is always in
* a known state (DISCONNECTED) when the driver is stopped.
* @return {@code true} if the operation succeeds, which means that the
* disconnect or stop command was initiated.
*/
public synchronized boolean disconnectAndStop() {
if (mRunState != RUN_STATE_STOPPING && mRunState != RUN_STATE_STOPPED) {
// Take down any open network notifications
setNotificationVisible(false, 0, false, 0);
mRunState = RUN_STATE_STOPPING;
if (mWifiInfo.getSupplicantState() == SupplicantState.DORMANT) {
return stopDriver();
} else {
return disconnect();
}
} else {
/*
* The "driver-stop" wake lock normally is released from the
* connectivity manager after the mobile data connection has
* been established, or after a timeout period, if that never
* happens. Because WifiService.updateWifiState() can get called
* multiple times, we can end up acquiring the wake lock and calling
* disconnectAndStop() even when a disconnect or stop operation
* is already in progress. In that case, we want to ignore the
* disconnectAndStop request and release the (ref-counted) wake
* lock, so that eventually, when the mobile data connection is
* established, the ref count will drop to zero.
*/
releaseWakeLock();
}
return true;
}
public synchronized boolean restart() {
if (mRunState == RUN_STATE_STOPPED) {
mRunState = RUN_STATE_STARTING;
resetConnections(true);
return startDriver();
} else if (mRunState == RUN_STATE_STOPPING) {
mRunState = RUN_STATE_STARTING;
}
return true;
}
/**
* TODO: add documentation to all the native calls
* along with conditional checks to make sure
* native calls dont happen when wifi is not enabled
*/
public synchronized boolean loadDriver() {
return WifiNative.loadDriver();
}
public synchronized boolean unloadDriver() {
return WifiNative.unloadDriver();
}
public synchronized boolean startSupplicant() {
return WifiNative.startSupplicant();
}
public synchronized boolean stopSupplicant() {
return WifiNative.stopSupplicant();
}
public synchronized boolean connectToSupplicant() {
return WifiNative.connectToSupplicant();
}
public synchronized void closeSupplicantConnection() {
WifiNative.closeSupplicantConnection();
}
public synchronized boolean ping() {
return WifiNative.pingCommand();
}
public synchronized boolean scan(boolean forceActive) {
return WifiNative.scanCommand(forceActive);
}
public synchronized boolean setScanResultHandling(int mode) {
return WifiNative.setScanResultHandlingCommand(mode);
}
public synchronized String scanResults() {
return WifiNative.scanResultsCommand();
}
public synchronized void setScanMode(boolean isScanModeActive) {
if (mIsScanModeActive != isScanModeActive) {
WifiNative.setScanModeCommand(mIsScanModeActive = isScanModeActive);
}
}
public synchronized boolean disconnect() {
return WifiNative.disconnectCommand();
}
public synchronized boolean reconnectCommand() {
return WifiNative.reconnectCommand();
}
public synchronized int addNetwork() {
return WifiNative.addNetworkCommand();
}
public synchronized boolean removeNetwork(int networkId) {
return mDisconnectExpected = WifiNative.removeNetworkCommand(networkId);
}
public synchronized boolean enableNetwork(int netId, boolean disableOthers) {
return WifiNative.enableNetworkCommand(netId, disableOthers);
}
public synchronized boolean disableNetwork(int netId) {
return WifiNative.disableNetworkCommand(netId);
}
public synchronized boolean reassociate() {
return WifiNative.reassociateCommand();
}
public synchronized boolean addToBlacklist(String bssid) {
return WifiNative.addToBlacklistCommand(bssid);
}
public synchronized boolean clearBlacklist() {
return WifiNative.clearBlacklistCommand();
}
public synchronized String listNetworks() {
return WifiNative.listNetworksCommand();
}
public synchronized String getNetworkVariable(int netId, String name) {
return WifiNative.getNetworkVariableCommand(netId, name);
}
public synchronized boolean setNetworkVariable(int netId, String name, String value) {
return WifiNative.setNetworkVariableCommand(netId, name, value);
}
public synchronized String status() {
return WifiNative.statusCommand();
}
public synchronized int getRssi() {
return WifiNative.getRssiApproxCommand();
}
public synchronized int getRssiApprox() {
return WifiNative.getRssiApproxCommand();
}
public synchronized int getLinkSpeed() {
return WifiNative.getLinkSpeedCommand();
}
public synchronized String getMacAddress() {
return WifiNative.getMacAddressCommand();
}
public synchronized boolean startDriver() {
return WifiNative.startDriverCommand();
}
public synchronized boolean stopDriver() {
return WifiNative.stopDriverCommand();
}
public synchronized boolean startPacketFiltering() {
return WifiNative.startPacketFiltering();
}
public synchronized boolean stopPacketFiltering() {
return WifiNative.stopPacketFiltering();
}
public synchronized boolean setPowerMode(int mode) {
return WifiNative.setPowerModeCommand(mode);
}
/**
* Set the number of allowed radio frequency channels from the system
* setting value, if any.
* @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
* the number of channels is invalid.
*/
public synchronized boolean setNumAllowedChannels() {
try {
return setNumAllowedChannels(
Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
} catch (Settings.SettingNotFoundException e) {
if (mNumAllowedChannels != 0) {
WifiNative.setNumAllowedChannelsCommand(mNumAllowedChannels);
}
// otherwise, use the driver default
}
return true;
}
/**
* Set the number of radio frequency channels that are allowed to be used
* in the current regulatory domain.
* @param numChannels the number of allowed channels. Must be greater than 0
* and less than or equal to 16.
* @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
* {@code numChannels} is outside the valid range.
*/
public synchronized boolean setNumAllowedChannels(int numChannels) {
mNumAllowedChannels = numChannels;
return WifiNative.setNumAllowedChannelsCommand(numChannels);
}
public synchronized int getNumAllowedChannels() {
return WifiNative.getNumAllowedChannelsCommand();
}
public synchronized boolean setBluetoothCoexistenceMode(int mode) {
return WifiNative.setBluetoothCoexistenceModeCommand(mode);
}
/**
* Enable or disable Bluetooth coexistence scan mode. When this mode is on,
* some of the low-level scan parameters used by the driver are changed to
* reduce interference with A2DP streaming.
*
* @param isBluetoothPlaying whether to enable or disable this mode
*/
public synchronized void setBluetoothScanMode(boolean isBluetoothPlaying) {
WifiNative.setBluetoothCoexistenceScanModeCommand(isBluetoothPlaying);
}
public synchronized boolean saveConfig() {
return WifiNative.saveConfigCommand();
}
public synchronized boolean reloadConfig() {
return WifiNative.reloadConfigCommand();
}
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);
}
/**
* Display or don't display a notification that there are open Wi-Fi networks.
* @param visible {@code true} if notification should be visible, {@code false} otherwise
* @param numNetworks the number networks seen
* @param force {@code true} to force notification to be shown/not-shown,
* even if it is already shown/not-shown.
* @param delay time in milliseconds after which the notification should be made
* visible or invisible.
*/
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;
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;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("interface ").append(mInterfaceName);
sb.append(" runState=");
if (mRunState >= 1 && mRunState <= mRunStateNames.length) {
sb.append(mRunStateNames[mRunState-1]);
} else {
sb.append(mRunState);
}
sb.append(LS).append(mWifiInfo).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(mTornDownByConnMgr);
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, null);
}
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
setBluetoothCoexistenceMode(
WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
}
setPowerMode(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());
}
setPowerMode(DRIVER_POWER_MODE_AUTO);
if (modifiedBluetoothCoexistenceMode) {
// Set the coexistence mode back to its default value
setBluetoothCoexistenceMode(
WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
}
synchronized (this) {
if (!mCancelCallback) {
mTarget.sendEmptyMessage(event);
}
}
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 int getMaxDhcpRetries() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
DEFAULT_MAX_DHCP_RETRIES);
}
private class SettingsObserver extends ContentObserver {
public SettingsObserver(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);
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();
if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
return;
}
boolean changed =
(wasStaticIp != mUseStaticIp) ||
(wasStaticIp && (
oIp != mDhcpInfo.ipAddress ||
oGw != mDhcpInfo.gateway ||
oMsk != mDhcpInfo.netmask ||
oDns1 != mDhcpInfo.dns1 ||
oDns2 != mDhcpInfo.dns2));
if (changed) {
resetConnections(true);
configureInterface();
if (mUseStaticIp) {
mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
}
}
}
}
private class NotificationEnabledSettingObserver extends ContentObserver {
public NotificationEnabledSettingObserver(Handler handler) {
super(handler);
}
public void register() {
ContentResolver cr = mContext.getContentResolver();
cr.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.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.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
}
}
}