| /* |
| * 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.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; |
| |
| /** |
| * 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; |
| |
| // 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; |
| |
| /** |
| * 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; |
| private Runnable mReleaseWakeLockCallback; |
| |
| /** |
| * 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"); |
| mDnsPropNames = new String[] { |
| "net." + mInterfaceName + ".dns1", |
| "net." + 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 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; |
| } |
| |
| private void noteRunState() { |
| try { |
| if (mRunState == RUN_STATE_RUNNING) { |
| mBatteryStats.noteWifiRunning(); |
| } else if (mRunState == RUN_STATE_STOPPED) { |
| mBatteryStats.noteWifiStopped(); |
| } |
| } catch (RemoteException ignore) { |
| } |
| } |
| |
| /** |
| * Set the run state to either "normal" or "scan-only". |
| * @param scanOnlyMode true if the new mode should be scan-only. |
| */ |
| public synchronized void setScanOnlyMode(boolean scanOnlyMode) { |
| // do nothing unless scan-only mode is changing |
| if (mIsScanOnly != scanOnlyMode) { |
| int scanType = (scanOnlyMode ? |
| SUPPL_SCAN_HANDLING_LIST_ONLY : SUPPL_SCAN_HANDLING_NORMAL); |
| if (LOCAL_LOGD) Log.v(TAG, "Scan-only mode changing to " + scanOnlyMode + " scanType=" + scanType); |
| if (setScanResultHandling(scanType)) { |
| mIsScanOnly = scanOnlyMode; |
| if (!isDriverStopped()) { |
| if (scanOnlyMode) { |
| disconnect(); |
| } else { |
| reconnectCommand(); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| private void checkIsBluetoothPlaying() { |
| boolean isBluetoothPlaying = false; |
| Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks(); |
| |
| for (BluetoothDevice device : connected) { |
| if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) { |
| isBluetoothPlaying = true; |
| break; |
| } |
| } |
| setBluetoothScanMode(isBluetoothPlaying); |
| } |
| |
| public void enableRssiPolling(boolean enable) { |
| if (mEnableRssiPolling != enable) { |
| mEnableRssiPolling = enable; |
| checkPollTimer(); |
| } |
| } |
| |
| /** |
| * 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; |
| noteRunState(); |
| 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; |
| mTornDownByConnMgr = false; |
| mLastBssid = null; |
| mLastSsid = null; |
| requestConnectionInfo(); |
| SupplicantState supplState = mWifiInfo.getSupplicantState(); |
| /** |
| * The MAC address isn't going to change, so just request it |
| * once here. |
| */ |
| String macaddr = getMacAddress(); |
| |
| if (macaddr != null) { |
| mWifiInfo.setMacAddress(macaddr); |
| } |
| if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant established, state=" + |
| supplState); |
| // Wi-Fi supplicant connection state changed: |
| // [31- 2] Reserved for future use |
| // [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) , |
| // or supplicant died (2) |
| EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, 1); |
| /* |
| * The COMPLETED state change from the supplicant may have occurred |
| * in between polling for supplicant availability, in which case |
| * we didn't perform a DHCP request to get an IP address. |
| */ |
| if (supplState == SupplicantState.COMPLETED) { |
| mLastBssid = mWifiInfo.getBSSID(); |
| mLastSsid = mWifiInfo.getSSID(); |
| configureInterface(); |
| } |
| if (ActivityManagerNative.isSystemReady()) { |
| intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); |
| intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, true); |
| mContext.sendBroadcast(intent); |
| } |
| if (supplState == SupplicantState.COMPLETED && mHaveIpAddress) { |
| setDetailedState(DetailedState.CONNECTED); |
| } else { |
| setDetailedState(WifiInfo.getDetailedStateOf(supplState)); |
| } |
| /* |
| * Filter out multicast packets. This saves battery power, since |
| * the CPU doesn't have to spend time processing packets that |
| * are going to end up being thrown away. |
| */ |
| mWM.initializeMulticastFiltering(); |
| |
| if (mBluetoothA2dp == null) { |
| mBluetoothA2dp = new BluetoothA2dp(mContext); |
| } |
| checkIsBluetoothPlaying(); |
| |
| // initialize this after the supplicant is alive |
| setNumAllowedChannels(); |
| break; |
| |
| case EVENT_SUPPLICANT_DISCONNECT: |
| mRunState = RUN_STATE_STOPPED; |
| noteRunState(); |
| 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 { |
| mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED); |
| } |
| if (LOCAL_LOGD) Log.v(TAG, "IP configuration: " + mDhcpInfo); |
| // Wi-Fi interface configuration state changed: |
| // [31- 1] Reserved for future use |
| // [ 0- 0] Interface configuration succeeded (1) or failed (0) |
| EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 1); |
| |
| // We've connected successfully, so allow the notification again in the future |
| resetNotificationTimer(); |
| break; |
| |
| case EVENT_INTERFACE_CONFIGURATION_FAILED: |
| if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) { |
| // Wi-Fi interface configuration state changed: |
| // [31- 1] Reserved for future use |
| // [ 0- 0] Interface configuration succeeded (1) or failed (0) |
| EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 0); |
| mHaveIpAddress = false; |
| mWifiInfo.setIpAddress(0); |
| mObtainingIpAddress = false; |
| disconnect(); |
| } |
| break; |
| |
| case EVENT_DRIVER_STATE_CHANGED: |
| // Wi-Fi driver state changed: |
| // 0 STARTED |
| // 1 STOPPED |
| // 2 HUNG |
| EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, msg.arg1); |
| |
| switch (msg.arg1) { |
| case DRIVER_STARTED: |
| /** |
| * Set the number of allowed radio channels according |
| * to the system setting, since it gets reset by the |
| * driver upon changing to the STARTED state. |
| */ |
| setNumAllowedChannels(); |
| synchronized (this) { |
| if (mRunState == RUN_STATE_STARTING) { |
| mRunState = RUN_STATE_RUNNING; |
| if (!mIsScanOnly) { |
| reconnectCommand(); |
| } else { |
| // In some situations, supplicant needs to be kickstarted to |
| // start the background scanning |
| scan(true); |
| } |
| } |
| } |
| break; |
| case DRIVER_HUNG: |
| Log.e(TAG, "Wifi Driver reports HUNG - reloading."); |
| /** |
| * restart the driver - toggle off and on |
| */ |
| mWM.setWifiEnabled(false); |
| mWM.setWifiEnabled(true); |
| break; |
| } |
| noteRunState(); |
| break; |
| |
| case EVENT_PASSWORD_KEY_MAY_BE_INCORRECT: |
| mPasswordKeyMayBeIncorrect = true; |
| break; |
| } |
| } |
| |
| private boolean wifiManagerDisableNetwork(int networkId) { |
| boolean disabledNetwork = false; |
| if (0 <= networkId) { |
| disabledNetwork = mWM.disableNetwork(networkId); |
| if (LOCAL_LOGD) { |
| if (disabledNetwork) { |
| Log.v(TAG, "Disabled network: " + networkId); |
| } |
| } |
| } |
| if (LOCAL_LOGD) { |
| if (!disabledNetwork) { |
| Log.e(TAG, "Failed to disable network:" + |
| " invalid network id: " + networkId); |
| } |
| } |
| return disabledNetwork; |
| } |
| |
| private void configureInterface() { |
| checkPollTimer(); |
| mLastSignalLevel = -1; |
| if (!mUseStaticIp) { |
| if (!mHaveIpAddress && !mObtainingIpAddress) { |
| mObtainingIpAddress = true; |
| mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START); |
| } |
| } else { |
| int event; |
| if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) { |
| mHaveIpAddress = true; |
| event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED; |
| if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration succeeded"); |
| } else { |
| mHaveIpAddress = false; |
| event = EVENT_INTERFACE_CONFIGURATION_FAILED; |
| if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed"); |
| } |
| sendEmptyMessage(event); |
| } |
| } |
| |
| /** |
| * Reset our IP state and send out broadcasts following a disconnect. |
| * @param newState the {@code DetailedState} to set. Should be either |
| * {@code DISCONNECTED} or {@code FAILED}. |
| * @param disableInterface indicates whether the interface should |
| * be disabled |
| */ |
| private void handleDisconnectedState(DetailedState newState, boolean disableInterface) { |
| if (mDisconnectPending) { |
| cancelDisconnect(); |
| } |
| mDisconnectExpected = false; |
| resetConnections(disableInterface); |
| setDetailedState(newState); |
| sendNetworkStateChangeBroadcast(mLastBssid); |
| mWifiInfo.setBSSID(null); |
| mLastBssid = null; |
| mLastSsid = null; |
| mDisconnectPending = false; |
| } |
| |
| /** |
| * Resets the Wi-Fi Connections by clearing any state, resetting any sockets |
| * using the interface, stopping DHCP, and disabling the interface. |
| */ |
| public void resetConnections(boolean disableInterface) { |
| if (LOCAL_LOGD) Log.d(TAG, "Reset connections and stopping DHCP"); |
| mHaveIpAddress = false; |
| mObtainingIpAddress = false; |
| mWifiInfo.setIpAddress(0); |
| |
| /* |
| * Reset connection depends on both the interface and the IP assigned, |
| * so it should be done before any chance of the IP being lost. |
| */ |
| NetworkUtils.resetConnections(mInterfaceName); |
| |
| // Stop DHCP |
| if (mDhcpTarget != null) { |
| mDhcpTarget.setCancelCallback(true); |
| mDhcpTarget.removeMessages(EVENT_DHCP_START); |
| } |
| if (!NetworkUtils.stopDhcp(mInterfaceName)) { |
| Log.e(TAG, "Could not stop DHCP"); |
| } |
| |
| /** |
| * Interface is re-enabled in the supplicant |
| * when moving out of ASSOCIATING state |
| */ |
| if(disableInterface) { |
| if (LOCAL_LOGD) Log.d(TAG, "Disabling interface"); |
| NetworkUtils.disableInterface(mInterfaceName); |
| } |
| } |
| |
| /** |
| * The supplicant is reporting that we are disconnected from the current |
| * access point. Often, however, a disconnect will be followed very shortly |
| * by a reconnect to the same access point. Therefore, we delay resetting |
| * the connection's IP state for a bit. |
| */ |
| private void scheduleDisconnect() { |
| mDisconnectPending = true; |
| if (!hasMessages(EVENT_DEFERRED_DISCONNECT)) { |
| sendEmptyMessageDelayed(EVENT_DEFERRED_DISCONNECT, DISCONNECT_DELAY_MSECS); |
| } |
| } |
| |
| private void cancelDisconnect() { |
| mDisconnectPending = false; |
| removeMessages(EVENT_DEFERRED_DISCONNECT); |
| } |
| |
| public DhcpInfo getDhcpInfo() { |
| return mDhcpInfo; |
| } |
| |
| public synchronized List<ScanResult> getScanResultsList() { |
| return mScanResults; |
| } |
| |
| public synchronized void setScanResultsList(List<ScanResult> scanList) { |
| mScanResults = scanList; |
| } |
| |
| /** |
| * Get status information for the current connection, if any. |
| * @return a {@link WifiInfo} object containing information about the current connection |
| */ |
| public WifiInfo requestConnectionInfo() { |
| requestConnectionStatus(mWifiInfo); |
| requestPolledInfo(mWifiInfo, false); |
| return mWifiInfo; |
| } |
| |
| private void requestConnectionStatus(WifiInfo info) { |
| String reply = status(); |
| if (reply == null) { |
| return; |
| } |
| /* |
| * Parse the reply from the supplicant to the status command, and update |
| * local state accordingly. The reply is a series of lines of the form |
| * "name=value". |
| */ |
| String SSID = null; |
| String BSSID = null; |
| String suppState = null; |
| int netId = -1; |
| String[] lines = reply.split("\n"); |
| for (String line : lines) { |
| String[] prop = line.split(" *= *", 2); |
| 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); |
| } |
| |
| /** |
| * 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; |
| } |
| return WifiNative.enableNetworkCommand(netId, disableOthers); |
| } |
| |
| /** |
| * 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; |
| } |
| 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 |
| * @return {@code true} if the operation succeeds, {@code false} otherwise |
| */ |
| public synchronized boolean setPowerMode(int mode) { |
| if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) { |
| return false; |
| } |
| 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) { |
| 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; |
| } |
| } |
| } |