We now distribute "wifi started" time across all apps that are holding WIFI locks that cause it to be started. But only when WIFI would not normally be running. Also have a mechanism to distribute other WIFI work that has happened across those processes based on their use. Also fixed a bug where we were not retaining the CPU speed step stats across boots...! Change-Id: I00e3153b98429166273750512cc37e7975211ab9
2687 lines
100 KiB
Java
2687 lines
100 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 static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
|
|
import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
|
|
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
|
|
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
|
|
import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
|
|
|
|
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.os.WorkSource;
|
|
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.net.UnknownHostException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
/**
|
|
* 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 = 9;
|
|
|
|
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;
|
|
|
|
/* Tracks if any network in the configuration is disabled */
|
|
private AtomicBoolean mIsAnyNetworkDisabled = new AtomicBoolean(false);
|
|
|
|
// 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;
|
|
private boolean mIsHighPerfEnabled;
|
|
private int mPowerModeRefCount = 0;
|
|
private int mOptimizationsDisabledRefCount = 0;
|
|
|
|
/**
|
|
* One of {@link WifiManager#WIFI_STATE_DISABLED},
|
|
* {@link WifiManager#WIFI_STATE_DISABLING},
|
|
* {@link WifiManager#WIFI_STATE_ENABLED},
|
|
* {@link WifiManager#WIFI_STATE_ENABLING},
|
|
* {@link WifiManager#WIFI_STATE_UNKNOWN}
|
|
*
|
|
* getWifiState() is not synchronized to make sure it's always fast,
|
|
* even when the instance lock is held on other slow operations.
|
|
* Use a atomic variable for state.
|
|
*/
|
|
private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_UNKNOWN);
|
|
|
|
// 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 static String[] sDnsPropNames;
|
|
|
|
/**
|
|
* Keep track of whether we last told the battery stats we had started.
|
|
*/
|
|
private boolean mReportedRunning = false;
|
|
|
|
/**
|
|
* Most recently set source of starting WIFI.
|
|
*/
|
|
private final WorkSource mRunningWifiUids = new WorkSource();
|
|
|
|
/**
|
|
* The last reported UIDs that were responsible for starting WIFI.
|
|
*/
|
|
private final WorkSource mLastRunningWifiUids = new WorkSource();
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* TODO: mRunState is not synchronized in some places
|
|
* address this as part of re-architect.
|
|
*
|
|
* TODO: We are exposing an additional public synchronized call
|
|
* for a wakelock optimization in WifiService. Remove it
|
|
* when we handle the wakelock in ConnectivityService.
|
|
*/
|
|
public synchronized boolean isDriverStopped() {
|
|
return mRunState == RUN_STATE_STOPPED || mRunState == RUN_STATE_STOPPING;
|
|
}
|
|
|
|
public void updateBatteryWorkSourceLocked(WorkSource newSource) {
|
|
try {
|
|
if (newSource != null) {
|
|
mRunningWifiUids.set(newSource);
|
|
}
|
|
if (mRunState == RUN_STATE_RUNNING) {
|
|
if (mReportedRunning) {
|
|
// If the work source has changed since last time, need
|
|
// to remove old work from battery stats.
|
|
if (mLastRunningWifiUids.diff(mRunningWifiUids)) {
|
|
mBatteryStats.noteWifiRunningChanged(mLastRunningWifiUids,
|
|
mRunningWifiUids);
|
|
mLastRunningWifiUids.set(mRunningWifiUids);
|
|
}
|
|
} else {
|
|
// Now being started, report it.
|
|
mBatteryStats.noteWifiRunning(mRunningWifiUids);
|
|
mLastRunningWifiUids.set(mRunningWifiUids);
|
|
mReportedRunning = true;
|
|
}
|
|
} else if (mRunState == RUN_STATE_STOPPED) {
|
|
if (mReportedRunning) {
|
|
// Last reported we were running, time to stop.
|
|
mBatteryStats.noteWifiStopped(mLastRunningWifiUids);
|
|
mLastRunningWifiUids.clear();
|
|
mReportedRunning = false;
|
|
}
|
|
} else {
|
|
// State in transition -- nothing to update yet.
|
|
}
|
|
} 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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set suspend mode optimizations. These include:
|
|
* - packet filtering
|
|
* - turn off roaming
|
|
* - DTIM settings
|
|
*
|
|
* Uses reference counting to keep the suspend optimizations disabled
|
|
* as long as one entity wants optimizations disabled.
|
|
*
|
|
* For example, WifiLock can keep suspend optimizations disabled
|
|
* or the user setting (wifi never sleeps) can keep suspend optimizations
|
|
* disabled. As long as one entity wants it disabled, it should stay
|
|
* that way
|
|
*
|
|
* @param enabled true if optimizations need enabled, false otherwise
|
|
*/
|
|
public synchronized void setSuspendModeOptimizations(boolean enabled) {
|
|
|
|
/* It is good to plumb suspend optimization enable
|
|
* or disable even if ref count indicates already done
|
|
* since we could have a case of previous failure.
|
|
*/
|
|
if (!enabled) {
|
|
mOptimizationsDisabledRefCount++;
|
|
} else {
|
|
mOptimizationsDisabledRefCount--;
|
|
if (mOptimizationsDisabledRefCount > 0) {
|
|
return;
|
|
} else {
|
|
/* Keep refcount from becoming negative */
|
|
mOptimizationsDisabledRefCount = 0;
|
|
}
|
|
}
|
|
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return;
|
|
}
|
|
|
|
WifiNative.setSuspendOptimizationsCommand(enabled);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set high performance mode of operation. This would mean
|
|
* use active power mode and disable suspend optimizations
|
|
* @param enabled true if enabled, false otherwise
|
|
*/
|
|
public synchronized void setHighPerfMode(boolean enabled) {
|
|
if (mIsHighPerfEnabled != enabled) {
|
|
if (enabled) {
|
|
setPowerMode(DRIVER_POWER_MODE_ACTIVE);
|
|
setSuspendModeOptimizations(false);
|
|
} else {
|
|
setPowerMode(DRIVER_POWER_MODE_AUTO);
|
|
setSuspendModeOptimizations(true);
|
|
}
|
|
mIsHighPerfEnabled = enabled;
|
|
Log.d(TAG,"high performance mode: " + enabled);
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We release the wakelock in WifiService
|
|
* using a timer.
|
|
*
|
|
* TODO:
|
|
* Releasing wakelock using both timer and
|
|
* a call from ConnectivityService requires
|
|
* a rethink. We had problems where WifiService
|
|
* could keep a wakelock forever if we delete
|
|
* messages in the asynchronous call
|
|
* from ConnectivityService
|
|
*/
|
|
@Override
|
|
public void releaseWakeLock() {
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
synchronized (this) {
|
|
updateBatteryWorkSourceLocked(null);
|
|
}
|
|
checkUseStaticIp();
|
|
/* Reset notification state on new connection */
|
|
resetNotificationTimer();
|
|
/*
|
|
* 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;
|
|
mIsHighPerfEnabled = false;
|
|
mOptimizationsDisabledRefCount = 0;
|
|
mPowerModeRefCount = 0;
|
|
mTornDownByConnMgr = false;
|
|
mLastBssid = null;
|
|
mLastSsid = null;
|
|
mIsAnyNetworkDisabled.set(false);
|
|
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;
|
|
synchronized (this) {
|
|
updateBatteryWorkSourceLocked(null);
|
|
}
|
|
boolean died = mWifiState.get() != WIFI_STATE_DISABLED &&
|
|
mWifiState.get() != 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:
|
|
/**
|
|
* mLastBssid can be null when there is a reconnect
|
|
* request on the first BSSID we connect to
|
|
*/
|
|
String BSSID = (msg.obj != null) ? msg.obj.toString() : null;
|
|
/**
|
|
* If we've exceeded the maximum number of retries for reconnecting
|
|
* to a given network, disable the network
|
|
*/
|
|
if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
|
|
if (++mReconnectCount > getMaxDhcpRetries()) {
|
|
if (LOCAL_LOGD) {
|
|
Log.d(TAG, "Failed reconnect count: " +
|
|
mReconnectCount + " Disabling " + BSSID);
|
|
}
|
|
mWM.disableNetwork(mLastNetworkId);
|
|
}
|
|
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 {
|
|
msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
|
|
msg.sendToTarget();
|
|
}
|
|
if (LOCAL_LOGD) Log.v(TAG, "IP configuration: " + mDhcpInfo);
|
|
// Wi-Fi interface configuration state changed:
|
|
// [31- 1] Reserved for future use
|
|
// [ 0- 0] Interface configuration succeeded (1) or failed (0)
|
|
EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 1);
|
|
|
|
// We've connected successfully, so allow the notification again in the future
|
|
resetNotificationTimer();
|
|
break;
|
|
|
|
case EVENT_INTERFACE_CONFIGURATION_FAILED:
|
|
if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
|
|
// Wi-Fi interface configuration state changed:
|
|
// [31- 1] Reserved for future use
|
|
// [ 0- 0] Interface configuration succeeded (1) or failed (0)
|
|
EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 0);
|
|
mHaveIpAddress = false;
|
|
mWifiInfo.setIpAddress(0);
|
|
mObtainingIpAddress = false;
|
|
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;
|
|
}
|
|
synchronized (this) {
|
|
updateBatteryWorkSourceLocked(null);
|
|
}
|
|
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() {
|
|
boolean ret = true;;
|
|
if (mRunState != RUN_STATE_STOPPING && mRunState != RUN_STATE_STOPPED) {
|
|
// Take down any open network notifications
|
|
setNotificationVisible(false, 0, false, 0);
|
|
|
|
if (mWifiInfo.getSupplicantState() == SupplicantState.DORMANT) {
|
|
ret = stopDriver();
|
|
} else {
|
|
ret = disconnect();
|
|
}
|
|
mRunState = RUN_STATE_STOPPING;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public int getWifiState() {
|
|
return mWifiState.get();
|
|
}
|
|
|
|
public void setWifiState(int wifiState) {
|
|
mWifiState.set(wifiState);
|
|
}
|
|
|
|
public boolean isAnyNetworkDisabled() {
|
|
return mIsAnyNetworkDisabled.get();
|
|
}
|
|
|
|
/**
|
|
* The WifiNative interface functions are listed below.
|
|
* The only native call that is not synchronized on
|
|
* WifiStateTracker is waitForEvent() which waits on a
|
|
* seperate monitor channel.
|
|
*
|
|
* All supplicant commands need the wifi to be in an
|
|
* enabled state. This can be done by checking the
|
|
* mWifiState to be WIFI_STATE_ENABLED.
|
|
*
|
|
* All commands that can cause commands to driver
|
|
* initiated need the driver state to be started.
|
|
* This is done by checking isDriverStopped() to
|
|
* be false.
|
|
*/
|
|
|
|
/**
|
|
* Load the driver and firmware
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean loadDriver() {
|
|
return WifiNative.loadDriver();
|
|
}
|
|
|
|
/**
|
|
* Unload the driver and firmware
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean unloadDriver() {
|
|
return WifiNative.unloadDriver();
|
|
}
|
|
|
|
/**
|
|
* Check the supplicant config and
|
|
* start the supplicant daemon
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean startSupplicant() {
|
|
return WifiNative.startSupplicant();
|
|
}
|
|
|
|
/**
|
|
* Stop the supplicant daemon
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean stopSupplicant() {
|
|
return WifiNative.stopSupplicant();
|
|
}
|
|
|
|
/**
|
|
* Establishes two channels - control channel for commands
|
|
* and monitor channel for notifying WifiMonitor
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean connectToSupplicant() {
|
|
return WifiNative.connectToSupplicant();
|
|
}
|
|
|
|
/**
|
|
* Close the control/monitor channels to supplicant
|
|
*/
|
|
public synchronized void closeSupplicantConnection() {
|
|
WifiNative.closeSupplicantConnection();
|
|
}
|
|
|
|
/**
|
|
* Check if the supplicant is alive
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean ping() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return WifiNative.pingCommand();
|
|
}
|
|
|
|
/**
|
|
* initiate an active or passive scan
|
|
*
|
|
* @param forceActive true if it is a active scan
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean scan(boolean forceActive) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
return WifiNative.scanCommand(forceActive);
|
|
}
|
|
|
|
/**
|
|
* Specifies whether the supplicant or driver
|
|
* take care of initiating scan and doing AP selection
|
|
*
|
|
* @param mode
|
|
* SUPPL_SCAN_HANDLING_NORMAL
|
|
* SUPPL_SCAN_HANDLING_LIST_ONLY
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean setScanResultHandling(int mode) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return WifiNative.setScanResultHandlingCommand(mode);
|
|
}
|
|
|
|
/**
|
|
* Fetch the scan results from the supplicant
|
|
*
|
|
* @return example result string
|
|
* 00:bb:cc:dd:cc:ee 2427 166 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net1
|
|
* 00:bb:cc:dd:cc:ff 2412 165 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net2
|
|
*/
|
|
public synchronized String scanResults() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return null;
|
|
}
|
|
return WifiNative.scanResultsCommand();
|
|
}
|
|
|
|
/**
|
|
* Set the scan mode - active or passive
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean setScanMode(boolean isScanModeActive) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
if (mIsScanModeActive != isScanModeActive) {
|
|
return WifiNative.setScanModeCommand(mIsScanModeActive = isScanModeActive);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Disconnect from Access Point
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean disconnect() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
return WifiNative.disconnectCommand();
|
|
}
|
|
|
|
/**
|
|
* Initiate a reconnection to AP
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean reconnectCommand() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
return WifiNative.reconnectCommand();
|
|
}
|
|
|
|
/**
|
|
* Add a network
|
|
*
|
|
* @return network id of the new network
|
|
*/
|
|
public synchronized int addNetwork() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return -1;
|
|
}
|
|
return WifiNative.addNetworkCommand();
|
|
}
|
|
|
|
/**
|
|
* Delete a network
|
|
*
|
|
* @param networkId id of the network to be removed
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean removeNetwork(int networkId) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return mDisconnectExpected = WifiNative.removeNetworkCommand(networkId);
|
|
}
|
|
|
|
/**
|
|
* Enable a network
|
|
*
|
|
* @param netId network id of the network
|
|
* @param disableOthers true, if all other networks have to be disabled
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean enableNetwork(int netId, boolean disableOthers) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
if (disableOthers) mIsAnyNetworkDisabled.set(true);
|
|
return WifiNative.enableNetworkCommand(netId, disableOthers);
|
|
}
|
|
|
|
/**
|
|
* Enable all networks
|
|
*
|
|
* @param networks list of configured networks
|
|
*/
|
|
public synchronized void enableAllNetworks(List<WifiConfiguration> networks) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return;
|
|
}
|
|
mIsAnyNetworkDisabled.set(false);
|
|
for (WifiConfiguration config : networks) {
|
|
if (config.status == WifiConfiguration.Status.DISABLED) {
|
|
WifiNative.enableNetworkCommand(config.networkId, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable a network
|
|
*
|
|
* @param netId network id of the network
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean disableNetwork(int netId) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
mIsAnyNetworkDisabled.set(true);
|
|
return WifiNative.disableNetworkCommand(netId);
|
|
}
|
|
|
|
/**
|
|
* Initiate a re-association in supplicant
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean reassociate() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
return WifiNative.reassociateCommand();
|
|
}
|
|
|
|
/**
|
|
* Blacklist a BSSID. This will avoid the AP if there are
|
|
* alternate APs to connect
|
|
*
|
|
* @param bssid BSSID of the network
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean addToBlacklist(String bssid) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return WifiNative.addToBlacklistCommand(bssid);
|
|
}
|
|
|
|
/**
|
|
* Clear the blacklist list
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean clearBlacklist() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return WifiNative.clearBlacklistCommand();
|
|
}
|
|
|
|
/**
|
|
* List all configured networks
|
|
*
|
|
* @return list of networks or null on failure
|
|
*/
|
|
public synchronized String listNetworks() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return null;
|
|
}
|
|
return WifiNative.listNetworksCommand();
|
|
}
|
|
|
|
/**
|
|
* Get network setting by name
|
|
*
|
|
* @param netId network id of the network
|
|
* @param name network variable key
|
|
* @return value corresponding to key
|
|
*/
|
|
public synchronized String getNetworkVariable(int netId, String name) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return null;
|
|
}
|
|
return WifiNative.getNetworkVariableCommand(netId, name);
|
|
}
|
|
|
|
/**
|
|
* Set network setting by name
|
|
*
|
|
* @param netId network id of the network
|
|
* @param name network variable key
|
|
* @param value network variable value
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean setNetworkVariable(int netId, String name, String value) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return WifiNative.setNetworkVariableCommand(netId, name, value);
|
|
}
|
|
|
|
/**
|
|
* Get detailed status of the connection
|
|
*
|
|
* @return Example status result
|
|
* bssid=aa:bb:cc:dd:ee:ff
|
|
* ssid=TestNet
|
|
* id=3
|
|
* pairwise_cipher=NONE
|
|
* group_cipher=NONE
|
|
* key_mgmt=NONE
|
|
* wpa_state=COMPLETED
|
|
* ip_address=X.X.X.X
|
|
*/
|
|
public synchronized String status() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return null;
|
|
}
|
|
return WifiNative.statusCommand();
|
|
}
|
|
|
|
/**
|
|
* Get RSSI to currently connected network
|
|
*
|
|
* @return RSSI value, -1 on failure
|
|
*/
|
|
public synchronized int getRssi() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return -1;
|
|
}
|
|
return WifiNative.getRssiApproxCommand();
|
|
}
|
|
|
|
/**
|
|
* Get approx RSSI to currently connected network
|
|
*
|
|
* @return RSSI value, -1 on failure
|
|
*/
|
|
public synchronized int getRssiApprox() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return -1;
|
|
}
|
|
return WifiNative.getRssiApproxCommand();
|
|
}
|
|
|
|
/**
|
|
* Get link speed to currently connected network
|
|
*
|
|
* @return link speed, -1 on failure
|
|
*/
|
|
public synchronized int getLinkSpeed() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return -1;
|
|
}
|
|
return WifiNative.getLinkSpeedCommand();
|
|
}
|
|
|
|
/**
|
|
* Get MAC address of radio
|
|
*
|
|
* @return MAC address, null on failure
|
|
*/
|
|
public synchronized String getMacAddress() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return null;
|
|
}
|
|
return WifiNative.getMacAddressCommand();
|
|
}
|
|
|
|
/**
|
|
* Start driver
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean startDriver() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return WifiNative.startDriverCommand();
|
|
}
|
|
|
|
/**
|
|
* Stop driver
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean stopDriver() {
|
|
/* Driver stop should not happen only when supplicant event
|
|
* DRIVER_STOPPED has already been handled */
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || mRunState == RUN_STATE_STOPPED) {
|
|
return false;
|
|
}
|
|
return WifiNative.stopDriverCommand();
|
|
}
|
|
|
|
/**
|
|
* Start packet filtering
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean startPacketFiltering() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
return WifiNative.startPacketFiltering();
|
|
}
|
|
|
|
/**
|
|
* Stop packet filtering
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean stopPacketFiltering() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
return WifiNative.stopPacketFiltering();
|
|
}
|
|
|
|
/**
|
|
* Get power mode
|
|
* @return power mode
|
|
*/
|
|
public synchronized int getPowerMode() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED && !isDriverStopped()) {
|
|
return -1;
|
|
}
|
|
return WifiNative.getPowerModeCommand();
|
|
}
|
|
|
|
/**
|
|
* Set power mode
|
|
* @param mode
|
|
* DRIVER_POWER_MODE_AUTO
|
|
* DRIVER_POWER_MODE_ACTIVE
|
|
*
|
|
* Uses reference counting to keep power mode active
|
|
* as long as one entity wants power mode to be active.
|
|
*
|
|
* For example, WifiLock high perf mode can keep power mode active
|
|
* or a DHCP session can keep it active. As long as one entity wants
|
|
* it enabled, it should stay that way
|
|
*
|
|
*/
|
|
private synchronized void setPowerMode(int mode) {
|
|
|
|
/* It is good to plumb power mode change
|
|
* even if ref count indicates already done
|
|
* since we could have a case of previous failure.
|
|
*/
|
|
switch(mode) {
|
|
case DRIVER_POWER_MODE_ACTIVE:
|
|
mPowerModeRefCount++;
|
|
break;
|
|
case DRIVER_POWER_MODE_AUTO:
|
|
mPowerModeRefCount--;
|
|
if (mPowerModeRefCount > 0) {
|
|
return;
|
|
} else {
|
|
/* Keep refcount from becoming negative */
|
|
mPowerModeRefCount = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
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() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
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) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
mNumAllowedChannels = numChannels;
|
|
return WifiNative.setNumAllowedChannelsCommand(numChannels);
|
|
}
|
|
|
|
/**
|
|
* Get number of allowed channels
|
|
*
|
|
* @return channel count, -1 on failure
|
|
*/
|
|
public synchronized int getNumAllowedChannels() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return -1;
|
|
}
|
|
return WifiNative.getNumAllowedChannelsCommand();
|
|
}
|
|
|
|
/**
|
|
* Set bluetooth coex mode:
|
|
*
|
|
* @param mode
|
|
* BLUETOOTH_COEXISTENCE_MODE_ENABLED
|
|
* BLUETOOTH_COEXISTENCE_MODE_DISABLED
|
|
* BLUETOOTH_COEXISTENCE_MODE_SENSE
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean setBluetoothCoexistenceMode(int mode) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return false;
|
|
}
|
|
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) {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
|
|
return;
|
|
}
|
|
WifiNative.setBluetoothCoexistenceScanModeCommand(isBluetoothPlaying);
|
|
}
|
|
|
|
/**
|
|
* Save configuration on supplicant
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean saveConfig() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
return WifiNative.saveConfigCommand();
|
|
}
|
|
|
|
/**
|
|
* Reload the configuration from file
|
|
*
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public synchronized boolean reloadConfig() {
|
|
if (mWifiState.get() != WIFI_STATE_ENABLED) {
|
|
return false;
|
|
}
|
|
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;
|
|
int powerMode = DRIVER_POWER_MODE_AUTO;
|
|
|
|
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);
|
|
}
|
|
|
|
powerMode = getPowerMode();
|
|
if (powerMode < 0) {
|
|
// Handle the case where supplicant driver does not support
|
|
// getPowerModeCommand.
|
|
powerMode = DRIVER_POWER_MODE_AUTO;
|
|
}
|
|
if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
|
|
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());
|
|
}
|
|
|
|
if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
|
|
setPowerMode(powerMode);
|
|
}
|
|
|
|
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(mBluetoothHeadset.getCurrentHeadset());
|
|
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) {
|
|
Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
|
|
msg.sendToTarget();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|