7c9bf7caa2
The supplicant can take up to 15 seconds to start - setting the number of wifi channels immediately after requested wifi start often will fail. Changed to set the number of channels when the supplicant is reported as alive. bug:2083601
1943 lines
77 KiB
Java
1943 lines
77 KiB
Java
/*
|
|
* 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;
|
|
|
|
/**
|
|
* 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 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;
|
|
|
|
/**
|
|
* 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, 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, 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, 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 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
|
|
synchronized (this) {
|
|
WifiNative.setScanResultHandlingCommand(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, 0, 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, 1, 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 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 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);
|
|
}
|
|
|
|
/**
|
|
* 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 (WifiNative.setScanResultHandlingCommand(scanType)) {
|
|
mIsScanOnly = scanOnlyMode;
|
|
if (!isDriverStopped()) {
|
|
if (scanOnlyMode) {
|
|
WifiNative.disconnectCommand();
|
|
} else {
|
|
WifiNative.reconnectCommand();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
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;
|
|
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- 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.
|
|
*
|
|
* Note that rather than turn this off directly, we use the
|
|
* public api - this keeps us all in sync - turn multicast on
|
|
* first and then off.. if nobody else wants it on it'll be
|
|
* off then and it's all synchronized within the API.
|
|
*/
|
|
WifiManager.MulticastLock l =
|
|
mWM.createMulticastLock("WifiStateTracker");
|
|
l.acquire();
|
|
l.release();
|
|
|
|
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);
|
|
synchronized (this) {
|
|
WifiNative.closeSupplicantConnection();
|
|
}
|
|
if (died) {
|
|
resetInterface(false);
|
|
}
|
|
// 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_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) {
|
|
DetailedState newDetailedState;
|
|
if (mIsScanOnly || mRunState == RUN_STATE_STOPPING) {
|
|
newDetailedState = DetailedState.IDLE;
|
|
} else {
|
|
newDetailedState = DetailedState.FAILED;
|
|
}
|
|
handleDisconnectedState(newDetailedState);
|
|
/**
|
|
* 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) {
|
|
sendEmptyMessageDelayed(EVENT_DEFERRED_RECONNECT, RECONNECT_DELAY_MSECS);
|
|
} else if (mRunState == RUN_STATE_STOPPING) {
|
|
synchronized (this) {
|
|
WifiNative.stopDriverCommand();
|
|
}
|
|
} else if (mRunState == RUN_STATE_STARTING && !mIsScanOnly) {
|
|
synchronized (this) {
|
|
WifiNative.reconnectCommand();
|
|
}
|
|
}
|
|
} else if (newState == SupplicantState.DISCONNECTED) {
|
|
if (isDriverStopped() || mDisconnectExpected) {
|
|
handleDisconnectedState(DetailedState.DISCONNECTED);
|
|
} 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.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();
|
|
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 network 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(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);
|
|
}
|
|
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 > getMaxDhcpRetries()) {
|
|
mWM.disableNetwork(mLastNetworkId);
|
|
}
|
|
synchronized(this) {
|
|
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);
|
|
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;
|
|
synchronized(this) {
|
|
WifiNative.disconnectCommand();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVENT_DRIVER_STATE_CHANGED:
|
|
boolean driverStarted = msg.arg1 != 0;
|
|
|
|
// 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) {
|
|
/**
|
|
* 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) {
|
|
WifiNative.reconnectCommand();
|
|
} else {
|
|
// In some situations, supplicant needs to be kickstarted to
|
|
// start the background scanning
|
|
WifiNative.scanCommand(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
public synchronized void setScanMode(boolean isScanModeActive) {
|
|
if (mIsScanModeActive != isScanModeActive) {
|
|
WifiNative.setScanModeCommand(mIsScanModeActive = isScanModeActive);
|
|
}
|
|
}
|
|
|
|
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}.
|
|
*/
|
|
private void handleDisconnectedState(DetailedState newState) {
|
|
if (LOCAL_LOGD) Log.d(TAG, "Deconfiguring interface and stopping DHCP");
|
|
if (mDisconnectPending) {
|
|
cancelDisconnect();
|
|
}
|
|
mDisconnectExpected = false;
|
|
resetInterface(true);
|
|
setDetailedState(newState);
|
|
sendNetworkStateChangeBroadcast(mLastBssid);
|
|
mWifiInfo.setBSSID(null);
|
|
mLastBssid = null;
|
|
mLastSsid = null;
|
|
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(boolean reenable) {
|
|
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");
|
|
}
|
|
|
|
NetworkUtils.disableInterface(mInterfaceName);
|
|
if (reenable) {
|
|
NetworkUtils.enableInterface(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;
|
|
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.
|
|
* @param info the object into which the information should be captured.
|
|
*/
|
|
private synchronized void requestPolledInfo(WifiInfo info, boolean polling)
|
|
{
|
|
int newRssi = WifiNative.getRssiCommand();
|
|
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 = WifiNative.getLinkSpeedCommand();
|
|
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.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 WifiNative.stopDriverCommand();
|
|
} else {
|
|
return WifiNative.disconnectCommand();
|
|
}
|
|
} 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;
|
|
resetInterface(true);
|
|
return WifiNative.startDriverCommand();
|
|
} else if (mRunState == RUN_STATE_STOPPING) {
|
|
mRunState = RUN_STATE_STARTING;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public synchronized boolean removeNetwork(int networkId) {
|
|
return mDisconnectExpected = WifiNative.removeNetworkCommand(networkId);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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("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
|
|
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) {
|
|
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) {
|
|
resetInterface(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;
|
|
}
|
|
}
|
|
}
|