summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/WifiService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/WifiService.java')
-rw-r--r--services/java/com/android/server/WifiService.java1844
1 files changed, 1844 insertions, 0 deletions
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
new file mode 100644
index 0000000..eece581
--- /dev/null
+++ b/services/java/com/android/server/WifiService.java
@@ -0,0 +1,1844 @@
+/*
+ * 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 com.android.server;
+
+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.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNative;
+import android.net.wifi.WifiStateTracker;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.NetworkStateTracker;
+import android.net.DhcpInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * WifiService handles remote WiFi operation requests by implementing
+ * the IWifiManager interface. It also creates a WifiMonitor to listen
+ * for Wifi-related events.
+ *
+ * @hide
+ */
+public class WifiService extends IWifiManager.Stub {
+ private static final String TAG = "WifiService";
+ private static final boolean DBG = false;
+ private static final Pattern scanResultPattern = Pattern.compile("\t+");
+ private final WifiStateTracker mWifiStateTracker;
+
+ private Context mContext;
+ private int mWifiState;
+
+ private AlarmManager mAlarmManager;
+ private PendingIntent mIdleIntent;
+ private static final int IDLE_REQUEST = 0;
+ private boolean mScreenOff;
+ private boolean mDeviceIdle;
+ private int mPluggedType;
+
+ private final LockList mLocks = new LockList();
+ /**
+ * See {@link Settings.Gservices#WIFI_IDLE_MS}. This is the default value if a
+ * Settings.Gservices value is not present. This timeout value is chosen as
+ * the approximate point at which the battery drain caused by Wi-Fi
+ * being enabled but not active exceeds the battery drain caused by
+ * re-establishing a connection to the mobile data network.
+ */
+ private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */
+
+ private static final String WAKELOCK_TAG = "WifiService";
+
+ /**
+ * The maximum amount of time to hold the wake lock after a disconnect
+ * caused by stopping the driver. Establishing an EDGE connection has been
+ * observed to take about 5 seconds under normal circumstances. This
+ * provides a bit of extra margin.
+ * <p>
+ * See {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}.
+ * This is the default value if a Settings.Secure value is not present.
+ */
+ private static final int DEFAULT_WAKELOCK_TIMEOUT = 8000;
+
+ // Wake lock used by driver-stop operation
+ private static PowerManager.WakeLock sDriverStopWakeLock;
+ // Wake lock used by other operations
+ private static PowerManager.WakeLock sWakeLock;
+
+ private static final int MESSAGE_ENABLE_WIFI = 0;
+ private static final int MESSAGE_DISABLE_WIFI = 1;
+ private static final int MESSAGE_STOP_WIFI = 2;
+ private static final int MESSAGE_START_WIFI = 3;
+ private static final int MESSAGE_RELEASE_WAKELOCK = 4;
+
+ private final WifiHandler mWifiHandler;
+
+ /*
+ * Map used to keep track of hidden networks presence, which
+ * is needed to switch between active and passive scan modes.
+ * If there is at least one hidden network that is currently
+ * present (enabled), we want to do active scans instead of
+ * passive.
+ */
+ private final Map<Integer, Boolean> mIsHiddenNetworkPresent;
+ /*
+ * The number of currently present hidden networks. When this
+ * counter goes from 0 to 1 or from 1 to 0, we change the
+ * scan mode to active or passive respectively. Initially, we
+ * set the counter to 0 and we increment it every time we add
+ * a new present (enabled) hidden network.
+ */
+ private int mNumHiddenNetworkPresent;
+ /*
+ * Whether we change the scan mode is due to a hidden network
+ * (in this class, this is always the case)
+ */
+ private final static boolean SET_DUE_TO_A_HIDDEN_NETWORK = true;
+
+ /*
+ * Cache of scan results objects (size is somewhat arbitrary)
+ */
+ private static final int SCAN_RESULT_CACHE_SIZE = 80;
+ private final LinkedHashMap<String, ScanResult> mScanResultCache;
+
+ /*
+ * Character buffer used to parse scan results (optimization)
+ */
+ private static final int SCAN_RESULT_BUFFER_SIZE = 512;
+ private char[] mScanResultBuffer;
+ private boolean mNeedReconfig;
+
+ /**
+ * Number of allowed radio frequency channels in various regulatory domains.
+ * This list is sufficient for 802.11b/g networks (2.4GHz range).
+ */
+ private static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14};
+
+ private static final String ACTION_DEVICE_IDLE =
+ "com.android.server.WifiManager.action.DEVICE_IDLE";
+
+ WifiService(Context context, WifiStateTracker tracker) {
+ mContext = context;
+ mWifiStateTracker = tracker;
+
+ /*
+ * Initialize the hidden-networks state
+ */
+ mIsHiddenNetworkPresent = new HashMap<Integer, Boolean>();
+ mNumHiddenNetworkPresent = 0;
+
+ mScanResultCache = new LinkedHashMap<String, ScanResult>(
+ SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
+ /*
+ * Limit the cache size by SCAN_RESULT_CACHE_SIZE
+ * elements
+ */
+ public boolean removeEldestEntry(Map.Entry eldest) {
+ return SCAN_RESULT_CACHE_SIZE < this.size();
+ }
+ };
+
+ mScanResultBuffer = new char [SCAN_RESULT_BUFFER_SIZE];
+
+ HandlerThread wifiThread = new HandlerThread("WifiService");
+ wifiThread.start();
+ mWifiHandler = new WifiHandler(wifiThread.getLooper());
+
+ mWifiState = WIFI_STATE_DISABLED;
+ boolean wifiEnabled = getPersistedWifiEnabled();
+
+ mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
+ mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
+
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+ sDriverStopWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+ mWifiStateTracker.setReleaseWakeLockCallback(
+ new Runnable() {
+ public void run() {
+ mWifiHandler.removeMessages(MESSAGE_RELEASE_WAKELOCK);
+ synchronized (sDriverStopWakeLock) {
+ if (sDriverStopWakeLock.isHeld()) {
+ sDriverStopWakeLock.release();
+ }
+ }
+ }
+ }
+ );
+
+ Log.i(TAG, "WifiService starting up with Wi-Fi " +
+ (wifiEnabled ? "enabled" : "disabled"));
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateWifiState();
+ }
+ },
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+
+ setWifiEnabledBlocking(wifiEnabled, false);
+ }
+
+ /**
+ * Initializes the hidden networks state. Must be called when we
+ * enable Wi-Fi.
+ */
+ private synchronized void initializeHiddenNetworksState() {
+ // First, reset the state
+ resetHiddenNetworksState();
+
+ // ... then add networks that are marked as hidden
+ List<WifiConfiguration> networks = getConfiguredNetworks();
+ if (!networks.isEmpty()) {
+ for (WifiConfiguration config : networks) {
+ if (config != null && config.hiddenSSID) {
+ addOrUpdateHiddenNetwork(
+ config.networkId,
+ config.status != WifiConfiguration.Status.DISABLED);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Resets the hidden networks state.
+ */
+ private synchronized void resetHiddenNetworksState() {
+ mNumHiddenNetworkPresent = 0;
+ mIsHiddenNetworkPresent.clear();
+ }
+
+ /**
+ * Marks all but netId network as not present.
+ */
+ private synchronized void markAllHiddenNetworksButOneAsNotPresent(int netId) {
+ for (Map.Entry<Integer, Boolean> entry : mIsHiddenNetworkPresent.entrySet()) {
+ if (entry != null) {
+ Integer networkId = entry.getKey();
+ if (networkId != netId) {
+ updateNetworkIfHidden(
+ networkId, false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the netId network presence status if netId is an existing
+ * hidden network.
+ */
+ private synchronized void updateNetworkIfHidden(int netId, boolean present) {
+ if (isHiddenNetwork(netId)) {
+ addOrUpdateHiddenNetwork(netId, present);
+ }
+ }
+
+ /**
+ * Updates the netId network presence status if netId is an existing
+ * hidden network. If the network does not exist, adds the network.
+ */
+ private synchronized void addOrUpdateHiddenNetwork(int netId, boolean present) {
+ if (0 <= netId) {
+
+ // If we are adding a new entry or modifying an existing one
+ Boolean isPresent = mIsHiddenNetworkPresent.get(netId);
+ if (isPresent == null || isPresent != present) {
+ if (present) {
+ incrementHiddentNetworkPresentCounter();
+ } else {
+ // If we add a new hidden network, no need to change
+ // the counter (it must be 0)
+ if (isPresent != null) {
+ decrementHiddentNetworkPresentCounter();
+ }
+ }
+ mIsHiddenNetworkPresent.put(netId, present);
+ }
+ } else {
+ Log.e(TAG, "addOrUpdateHiddenNetwork(): Invalid (negative) network id!");
+ }
+ }
+
+ /**
+ * Removes the netId network if it is hidden (being kept track of).
+ */
+ private synchronized void removeNetworkIfHidden(int netId) {
+ if (isHiddenNetwork(netId)) {
+ removeHiddenNetwork(netId);
+ }
+ }
+
+ /**
+ * Removes the netId network. For the call to be successful, the network
+ * must be hidden.
+ */
+ private synchronized void removeHiddenNetwork(int netId) {
+ if (0 <= netId) {
+ Boolean isPresent =
+ mIsHiddenNetworkPresent.remove(netId);
+ if (isPresent != null) {
+ // If we remove an existing hidden network that is not
+ // present, no need to change the counter
+ if (isPresent) {
+ decrementHiddentNetworkPresentCounter();
+ }
+ } else {
+ if (DBG) {
+ Log.d(TAG, "removeHiddenNetwork(): Removing a non-existent network!");
+ }
+ }
+ } else {
+ Log.e(TAG, "removeHiddenNetwork(): Invalid (negative) network id!");
+ }
+ }
+
+ /**
+ * Returns true if netId is an existing hidden network.
+ */
+ private synchronized boolean isHiddenNetwork(int netId) {
+ return mIsHiddenNetworkPresent.containsKey(netId);
+ }
+
+ /**
+ * Increments the present (enabled) hidden networks counter. If the
+ * counter value goes from 0 to 1, changes the scan mode to active.
+ */
+ private void incrementHiddentNetworkPresentCounter() {
+ ++mNumHiddenNetworkPresent;
+ if (1 == mNumHiddenNetworkPresent) {
+ // Switch the scan mode to "active"
+ mWifiStateTracker.setScanMode(true, SET_DUE_TO_A_HIDDEN_NETWORK);
+ }
+ }
+
+ /**
+ * Decrements the present (enabled) hidden networks counter. If the
+ * counter goes from 1 to 0, changes the scan mode back to passive.
+ */
+ private void decrementHiddentNetworkPresentCounter() {
+ if (0 < mNumHiddenNetworkPresent) {
+ --mNumHiddenNetworkPresent;
+ if (0 == mNumHiddenNetworkPresent) {
+ // Switch the scan mode to "passive"
+ mWifiStateTracker.setScanMode(false, SET_DUE_TO_A_HIDDEN_NETWORK);
+ }
+ } else {
+ Log.e(TAG, "Hidden-network counter invariant violation!");
+ }
+ }
+
+ private boolean getPersistedWifiEnabled() {
+ final ContentResolver cr = mContext.getContentResolver();
+ try {
+ return Settings.Secure.getInt(cr, Settings.Secure.WIFI_ON) == 1;
+ } catch (Settings.SettingNotFoundException e) {
+ Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, 0);
+ return false;
+ }
+ }
+
+ private void persistWifiEnabled(boolean enabled) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0);
+ }
+
+ NetworkStateTracker getNetworkStateTracker() {
+ return mWifiStateTracker;
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#pingSupplicant()}
+ * @return {@code true} if the operation succeeds
+ */
+ public boolean pingSupplicant() {
+ enforceChangePermission();
+ synchronized (mWifiStateTracker) {
+ return WifiNative.pingCommand();
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#startScan()}
+ * @return {@code true} if the operation succeeds
+ */
+ public boolean startScan() {
+ enforceChangePermission();
+ synchronized (mWifiStateTracker) {
+ switch (mWifiStateTracker.getSupplicantState()) {
+ case DISCONNECTED:
+ case INACTIVE:
+ case SCANNING:
+ case DORMANT:
+ break;
+ default:
+ WifiNative.setScanResultHandlingCommand(
+ WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
+ break;
+ }
+ return WifiNative.scanCommand();
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
+ * @param enable {@code true} to enable, {@code false} to disable.
+ * @return {@code true} if the enable/disable operation was
+ * started or is already in the queue.
+ */
+ public boolean setWifiEnabled(boolean enable) {
+ enforceChangePermission();
+ if (mWifiHandler == null) return false;
+
+ synchronized (mWifiHandler) {
+ sWakeLock.acquire();
+ sendEnableMessage(enable, true);
+ }
+
+ return true;
+ }
+
+ /**
+ * Enables/disables Wi-Fi synchronously.
+ * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
+ * @param persist {@code true} if the setting should be persisted.
+ * @return {@code true} if the operation succeeds (or if the existing state
+ * is the same as the requested state)
+ */
+ private boolean setWifiEnabledBlocking(boolean enable, boolean persist) {
+ final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED;
+
+ if (mWifiState == eventualWifiState) {
+ return true;
+ }
+ if (enable && isAirplaneModeOn()) {
+ return false;
+ }
+
+ setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING);
+
+ if (enable) {
+ if (!WifiNative.loadDriver()) {
+ Log.e(TAG, "Failed to load Wi-Fi driver.");
+ setWifiEnabledState(WIFI_STATE_UNKNOWN);
+ return false;
+ }
+ if (!WifiNative.startSupplicant()) {
+ WifiNative.unloadDriver();
+ Log.e(TAG, "Failed to start supplicant daemon.");
+ setWifiEnabledState(WIFI_STATE_UNKNOWN);
+ return false;
+ }
+ registerForBroadcasts();
+ mWifiStateTracker.startEventLoop();
+ } else {
+
+ mContext.unregisterReceiver(mReceiver);
+ // Remove notification (it will no-op if it isn't visible)
+ mWifiStateTracker.setNotificationVisible(false, 0, false, 0);
+
+ boolean failedToStopSupplicantOrUnloadDriver = false;
+ if (!WifiNative.stopSupplicant()) {
+ Log.e(TAG, "Failed to stop supplicant daemon.");
+ setWifiEnabledState(WIFI_STATE_UNKNOWN);
+ failedToStopSupplicantOrUnloadDriver = true;
+ }
+
+ // We must reset the interface before we unload the driver
+ mWifiStateTracker.resetInterface();
+
+ if (!WifiNative.unloadDriver()) {
+ Log.e(TAG, "Failed to unload Wi-Fi driver.");
+ if (!failedToStopSupplicantOrUnloadDriver) {
+ setWifiEnabledState(WIFI_STATE_UNKNOWN);
+ failedToStopSupplicantOrUnloadDriver = true;
+ }
+ }
+ if (failedToStopSupplicantOrUnloadDriver) {
+ return false;
+ }
+ }
+
+ // Success!
+
+ if (persist) {
+ persistWifiEnabled(enable);
+ }
+ setWifiEnabledState(eventualWifiState);
+
+ /*
+ * Initialize the hidden networks state and the number of allowed
+ * radio channels if Wi-Fi is being turned on.
+ */
+ if (enable) {
+ mWifiStateTracker.setNumAllowedChannels();
+ initializeHiddenNetworksState();
+ }
+
+ return true;
+ }
+
+ private void setWifiEnabledState(int wifiState) {
+ final int previousWifiState = mWifiState;
+
+ // Update state
+ mWifiState = wifiState;
+
+ // Broadcast
+ final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
+ intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
+ mContext.sendStickyBroadcast(intent);
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
+ "WifiService");
+ }
+
+ private void enforceChangePermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
+ "WifiService");
+
+ }
+
+ /**
+ * see {@link WifiManager#getWifiState()}
+ * @return 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}
+ */
+ public int getWifiEnabledState() {
+ enforceAccessPermission();
+ return mWifiState;
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#disconnect()}
+ * @return {@code true} if the operation succeeds
+ */
+ public boolean disconnect() {
+ enforceChangePermission();
+ synchronized (mWifiStateTracker) {
+ return WifiNative.disconnectCommand();
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#reconnect()}
+ * @return {@code true} if the operation succeeds
+ */
+ public boolean reconnect() {
+ enforceChangePermission();
+ synchronized (mWifiStateTracker) {
+ return WifiNative.reconnectCommand();
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#reassociate()}
+ * @return {@code true} if the operation succeeds
+ */
+ public boolean reassociate() {
+ enforceChangePermission();
+ synchronized (mWifiStateTracker) {
+ return WifiNative.reassociateCommand();
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
+ * @return the list of configured networks
+ */
+ public List<WifiConfiguration> getConfiguredNetworks() {
+ enforceAccessPermission();
+ String listStr;
+ /*
+ * We don't cache the list, because we want to allow
+ * for the possibility that the configuration file
+ * has been modified through some external means,
+ * such as the wpa_cli command line program.
+ */
+ synchronized (mWifiStateTracker) {
+ listStr = WifiNative.listNetworksCommand();
+ }
+ List<WifiConfiguration> networks =
+ new ArrayList<WifiConfiguration>();
+ if (listStr == null)
+ return networks;
+
+ String[] lines = listStr.split("\n");
+ // Skip the first line, which is a header
+ for (int i = 1; i < lines.length; i++) {
+ String[] result = lines[i].split("\t");
+ // network-id | ssid | bssid | flags
+ WifiConfiguration config = new WifiConfiguration();
+ try {
+ config.networkId = Integer.parseInt(result[0]);
+ } catch(NumberFormatException e) {
+ continue;
+ }
+ if (result.length > 3) {
+ if (result[3].indexOf("[CURRENT]") != -1)
+ config.status = WifiConfiguration.Status.CURRENT;
+ else if (result[3].indexOf("[DISABLED]") != -1)
+ config.status = WifiConfiguration.Status.DISABLED;
+ else
+ config.status = WifiConfiguration.Status.ENABLED;
+ } else
+ config.status = WifiConfiguration.Status.ENABLED;
+ synchronized (mWifiStateTracker) {
+ readNetworkVariables(config);
+ }
+ networks.add(config);
+ }
+
+ return networks;
+ }
+
+ /**
+ * Read the variables from the supplicant daemon that are needed to
+ * fill in the WifiConfiguration object.
+ * <p/>
+ * The caller must hold the synchronization monitor.
+ * @param config the {@link WifiConfiguration} object to be filled in.
+ */
+ private static void readNetworkVariables(WifiConfiguration config) {
+
+ int netId = config.networkId;
+ if (netId < 0)
+ return;
+
+ /*
+ * TODO: maybe should have a native method that takes an array of
+ * variable names and returns an array of values. But we'd still
+ * be doing a round trip to the supplicant daemon for each variable.
+ */
+ String value;
+
+ value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
+ if (!TextUtils.isEmpty(value)) {
+ config.SSID = value;
+ } else {
+ config.SSID = null;
+ }
+
+ value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName);
+ if (!TextUtils.isEmpty(value)) {
+ config.BSSID = value;
+ } else {
+ config.BSSID = null;
+ }
+
+ value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName);
+ config.priority = -1;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.priority = Integer.parseInt(value);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName);
+ config.hiddenSSID = false;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.hiddenSSID = Integer.parseInt(value) != 0;
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName);
+ config.wepTxKeyIndex = -1;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.wepTxKeyIndex = Integer.parseInt(value);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ /*
+ * Get up to 4 WEP keys. Note that the actual keys are not passed back,
+ * just a "*" if the key is set, or the null string otherwise.
+ */
+ for (int i = 0; i < 4; i++) {
+ value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepKeyVarNames[i]);
+ if (!TextUtils.isEmpty(value)) {
+ config.wepKeys[i] = value;
+ } else {
+ config.wepKeys[i] = null;
+ }
+ }
+
+ /*
+ * Get the private shared key. Note that the actual keys are not passed back,
+ * just a "*" if the key is set, or the null string otherwise.
+ */
+ value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName);
+ if (!TextUtils.isEmpty(value)) {
+ config.preSharedKey = value;
+ } else {
+ config.preSharedKey = null;
+ }
+
+ value = WifiNative.getNetworkVariableCommand(config.networkId,
+ WifiConfiguration.Protocol.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.Protocol.strings);
+ if (0 <= index) {
+ config.allowedProtocols.set(index);
+ }
+ }
+ }
+
+ value = WifiNative.getNetworkVariableCommand(config.networkId,
+ WifiConfiguration.KeyMgmt.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.KeyMgmt.strings);
+ if (0 <= index) {
+ config.allowedKeyManagement.set(index);
+ }
+ }
+ }
+
+ value = WifiNative.getNetworkVariableCommand(config.networkId,
+ WifiConfiguration.AuthAlgorithm.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
+ if (0 <= index) {
+ config.allowedAuthAlgorithms.set(index);
+ }
+ }
+ }
+
+ value = WifiNative.getNetworkVariableCommand(config.networkId,
+ WifiConfiguration.PairwiseCipher.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.PairwiseCipher.strings);
+ if (0 <= index) {
+ config.allowedPairwiseCiphers.set(index);
+ }
+ }
+ }
+
+ value = WifiNative.getNetworkVariableCommand(config.networkId,
+ WifiConfiguration.GroupCipher.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.GroupCipher.strings);
+ if (0 <= index) {
+ config.allowedGroupCiphers.set(index);
+ }
+ }
+ }
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
+ * @return the supplicant-assigned identifier for the new or updated
+ * network if the operation succeeds, or {@code -1} if it fails
+ */
+ public synchronized int addOrUpdateNetwork(WifiConfiguration config) {
+ enforceChangePermission();
+ /*
+ * If the supplied networkId is -1, we create a new empty
+ * network configuration. Otherwise, the networkId should
+ * refer to an existing configuration.
+ */
+ int netId = config.networkId;
+ boolean newNetwork = netId == -1;
+ boolean doReconfig;
+ int currentPriority;
+ // networkId of -1 means we want to create a new network
+ if (newNetwork) {
+ netId = WifiNative.addNetworkCommand();
+ if (netId < 0) {
+ if (DBG) {
+ Log.d(TAG, "Failed to add a network!");
+ }
+ return -1;
+ }
+ doReconfig = true;
+ } else {
+ String priorityVal = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName);
+ currentPriority = -1;
+ if (!TextUtils.isEmpty(priorityVal)) {
+ try {
+ currentPriority = Integer.parseInt(priorityVal);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ doReconfig = currentPriority != config.priority;
+ }
+ mNeedReconfig = mNeedReconfig || doReconfig;
+
+ /*
+ * If we have hidden networks, we may have to change the scan mode
+ */
+ if (config.hiddenSSID) {
+ // Mark the network as present unless it is disabled
+ addOrUpdateHiddenNetwork(
+ netId, config.status != WifiConfiguration.Status.DISABLED);
+ }
+
+ setVariables: {
+ /*
+ * Note that if a networkId for a non-existent network
+ * was supplied, then the first setNetworkVariableCommand()
+ * will fail, so we don't bother to make a separate check
+ * for the validity of the ID up front.
+ */
+
+ if (config.SSID != null &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.ssidVarName,
+ config.SSID)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set SSID: "+config.SSID);
+ }
+ break setVariables;
+ }
+
+ if (config.BSSID != null &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.bssidVarName,
+ config.BSSID)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set BSSID: "+config.BSSID);
+ }
+ break setVariables;
+ }
+
+ String allowedKeyManagementString =
+ makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
+ if (config.allowedKeyManagement.cardinality() != 0 &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.KeyMgmt.varName,
+ allowedKeyManagementString)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set key_mgmt: "+
+ allowedKeyManagementString);
+ }
+ break setVariables;
+ }
+
+ String allowedProtocolsString =
+ makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
+ if (config.allowedProtocols.cardinality() != 0 &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.Protocol.varName,
+ allowedProtocolsString)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set proto: "+
+ allowedProtocolsString);
+ }
+ break setVariables;
+ }
+
+ String allowedAuthAlgorithmsString =
+ makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
+ if (config.allowedAuthAlgorithms.cardinality() != 0 &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.AuthAlgorithm.varName,
+ allowedAuthAlgorithmsString)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set auth_alg: "+
+ allowedAuthAlgorithmsString);
+ }
+ break setVariables;
+ }
+
+ String allowedPairwiseCiphersString =
+ makeString(config.allowedPairwiseCiphers, WifiConfiguration.PairwiseCipher.strings);
+ if (config.allowedPairwiseCiphers.cardinality() != 0 &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.PairwiseCipher.varName,
+ allowedPairwiseCiphersString)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set pairwise: "+
+ allowedPairwiseCiphersString);
+ }
+ break setVariables;
+ }
+
+ String allowedGroupCiphersString =
+ makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
+ if (config.allowedGroupCiphers.cardinality() != 0 &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.GroupCipher.varName,
+ allowedGroupCiphersString)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set group: "+
+ allowedGroupCiphersString);
+ }
+ break setVariables;
+ }
+
+ // Prevent client screw-up by passing in a WifiConfiguration we gave it
+ // by preventing "*" as a key.
+ if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
+ !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.pskVarName,
+ config.preSharedKey)) {
+ if (DBG) {
+ Log.d(TAG, "failed to set psk: "+config.preSharedKey);
+ }
+ break setVariables;
+ }
+
+ boolean hasSetKey = false;
+ if (config.wepKeys != null) {
+ for (int i = 0; i < config.wepKeys.length; i++) {
+ // Prevent client screw-up by passing in a WifiConfiguration we gave it
+ // by preventing "*" as a key.
+ if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
+ if (!WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.wepKeyVarNames[i],
+ config.wepKeys[i])) {
+ if (DBG) {
+ Log.d(TAG,
+ "failed to set wep_key"+i+": " +
+ config.wepKeys[i]);
+ }
+ break setVariables;
+ }
+ hasSetKey = true;
+ }
+ }
+ }
+
+ if (hasSetKey) {
+ if (!WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.wepTxKeyIdxVarName,
+ Integer.toString(config.wepTxKeyIndex))) {
+ if (DBG) {
+ Log.d(TAG,
+ "failed to set wep_tx_keyidx: "+
+ config.wepTxKeyIndex);
+ }
+ break setVariables;
+ }
+ }
+
+ if (!WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.priorityVarName,
+ Integer.toString(config.priority))) {
+ if (DBG) {
+ Log.d(TAG, config.SSID + ": failed to set priority: "
+ +config.priority);
+ }
+ break setVariables;
+ }
+
+ if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand(
+ netId,
+ WifiConfiguration.hiddenSSIDVarName,
+ Integer.toString(config.hiddenSSID ? 1 : 0))) {
+ if (DBG) {
+ Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
+ config.hiddenSSID);
+ }
+ break setVariables;
+ }
+
+ return netId;
+ }
+
+ /*
+ * For an update, if one of the setNetworkVariable operations fails,
+ * we might want to roll back all the changes already made. But the
+ * chances are that if anything is going to go wrong, it'll happen
+ * the first time we try to set one of the variables.
+ */
+ if (newNetwork) {
+ removeNetwork(netId);
+ if (DBG) {
+ Log.d(TAG,
+ "Failed to set a network variable, removed network: "
+ + netId);
+ }
+ }
+ return -1;
+ }
+
+ private static String makeString(BitSet set, String[] strings) {
+ StringBuffer buf = new StringBuffer();
+ int nextSetBit = -1;
+
+ /* Make sure all set bits are in [0, strings.length) to avoid
+ * going out of bounds on strings. (Shouldn't happen, but...) */
+ set = set.get(0, strings.length);
+
+ while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
+ buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
+ }
+
+ // remove trailing space
+ if (set.cardinality() > 0) {
+ buf.setLength(buf.length() - 1);
+ }
+
+ return buf.toString();
+ }
+
+ private static int lookupString(String string, String[] strings) {
+ int size = strings.length;
+
+ string = string.replace('-', '_');
+
+ for (int i = 0; i < size; i++)
+ if (string.equals(strings[i]))
+ return i;
+
+ if (DBG) {
+ // if we ever get here, we should probably add the
+ // value to WifiConfiguration to reflect that it's
+ // supported by the WPA supplicant
+ Log.w(TAG, "Failed to look-up a string: " + string);
+ }
+
+ return -1;
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
+ * @param netId the integer that identifies the network configuration
+ * to the supplicant
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean removeNetwork(int netId) {
+ enforceChangePermission();
+
+ /*
+ * If we have hidden networks, we may have to change the scan mode
+ */
+ removeNetworkIfHidden(netId);
+
+ return mWifiStateTracker.removeNetwork(netId);
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#enableNetwork(int, boolean)}
+ * @param netId the integer that identifies the network configuration
+ * to the supplicant
+ * @param disableOthers if true, disable all other networks.
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean enableNetwork(int netId, boolean disableOthers) {
+ enforceChangePermission();
+
+ /*
+ * If we have hidden networks, we may have to change the scan mode
+ */
+ synchronized(this) {
+ if (disableOthers) {
+ markAllHiddenNetworksButOneAsNotPresent(netId);
+ }
+ updateNetworkIfHidden(netId, true);
+ }
+
+ synchronized (mWifiStateTracker) {
+ return WifiNative.enableNetworkCommand(netId, disableOthers);
+ }
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#disableNetwork(int)}
+ * @param netId the integer that identifies the network configuration
+ * to the supplicant
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean disableNetwork(int netId) {
+ enforceChangePermission();
+
+ /*
+ * If we have hidden networks, we may have to change the scan mode
+ */
+ updateNetworkIfHidden(netId, false);
+
+ synchronized (mWifiStateTracker) {
+ return WifiNative.disableNetworkCommand(netId);
+ }
+ }
+
+ /**
+ * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
+ * @return the Wi-Fi information, contained in {@link WifiInfo}.
+ */
+ public WifiInfo getConnectionInfo() {
+ enforceAccessPermission();
+ /*
+ * Make sure we have the latest information, by sending
+ * a status request to the supplicant.
+ */
+ return mWifiStateTracker.requestConnectionInfo();
+ }
+
+ /**
+ * Return the results of the most recent access point scan, in the form of
+ * a list of {@link ScanResult} objects.
+ * @return the list of results
+ */
+ public List<ScanResult> getScanResults() {
+ enforceAccessPermission();
+ String reply;
+ synchronized (mWifiStateTracker) {
+ reply = WifiNative.scanResultsCommand();
+ }
+ if (reply == null) {
+ return null;
+ }
+
+ List<ScanResult> scanList = new ArrayList<ScanResult>();
+
+ int lineCount = 0;
+
+ int replyLen = reply.length();
+ // Parse the result string, keeping in mind that the last line does
+ // not end with a newline.
+ for (int lineBeg = 0, lineEnd = 0; lineEnd <= replyLen; ++lineEnd) {
+ if (lineEnd == replyLen || reply.charAt(lineEnd) == '\n') {
+ ++lineCount;
+ /*
+ * Skip the first line, which is a header
+ */
+ if (lineCount == 1) {
+ lineBeg = lineEnd + 1;
+ continue;
+ }
+ int lineLen = lineEnd - lineBeg;
+ if (0 < lineLen && lineLen <= SCAN_RESULT_BUFFER_SIZE) {
+ int scanResultLevel = 0;
+ /*
+ * At most one thread should have access to the buffer at a time!
+ */
+ synchronized(mScanResultBuffer) {
+ boolean parsingScanResultLevel = false;
+ for (int i = lineBeg; i < lineEnd; ++i) {
+ char ch = reply.charAt(i);
+ /*
+ * Assume that the signal level starts with a '-'
+ */
+ if (ch == '-') {
+ /*
+ * Skip whatever instances of '-' we may have
+ * after we parse the signal level
+ */
+ parsingScanResultLevel = (scanResultLevel == 0);
+ } else if (parsingScanResultLevel) {
+ int digit = Character.digit(ch, 10);
+ if (0 <= digit) {
+ scanResultLevel =
+ 10 * scanResultLevel + digit;
+ /*
+ * Replace the signal level number in
+ * the string with 0's for caching
+ */
+ ch = '0';
+ } else {
+ /*
+ * Reset the flag if we meet a non-digit
+ * character
+ */
+ parsingScanResultLevel = false;
+ }
+ }
+ mScanResultBuffer[i - lineBeg] = ch;
+ }
+ if (scanResultLevel != 0) {
+ ScanResult scanResult = parseScanResult(
+ new String(mScanResultBuffer, 0, lineLen));
+ if (scanResult != null) {
+ scanResult.level = -scanResultLevel;
+ scanList.add(scanResult);
+ }
+ } else if (DBG) {
+ Log.w(TAG,
+ "ScanResult.level=0: misformatted scan result?");
+ }
+ }
+ } else if (0 < lineLen) {
+ if (DBG) {
+ Log.w(TAG, "Scan result line is too long: " +
+ (lineEnd - lineBeg) + ", skipping the line!");
+ }
+ }
+ lineBeg = lineEnd + 1;
+ }
+ }
+ mWifiStateTracker.setScanResultsList(scanList);
+ return scanList;
+ }
+
+ /**
+ * Parse the scan result line passed to us by wpa_supplicant (helper).
+ * @param line the line to parse
+ * @return the {@link ScanResult} object
+ */
+ private ScanResult parseScanResult(String line) {
+ ScanResult scanResult = null;
+ if (line != null) {
+ /*
+ * Cache implementation (LinkedHashMap) is not synchronized, thus,
+ * must synchronized here!
+ */
+ synchronized (mScanResultCache) {
+ scanResult = mScanResultCache.get(line);
+ if (scanResult == null) {
+ String[] result = scanResultPattern.split(line);
+ if (3 <= result.length && result.length <= 5) {
+ // bssid | frequency | level | flags | ssid
+ int frequency;
+ int level;
+ try {
+ frequency = Integer.parseInt(result[1]);
+ level = Integer.parseInt(result[2]);
+ } catch (NumberFormatException e) {
+ frequency = 0;
+ level = 0;
+ }
+
+ /*
+ * The formatting of the results returned by
+ * wpa_supplicant is intended to make the fields
+ * line up nicely when printed,
+ * not to make them easy to parse. So we have to
+ * apply some heuristics to figure out which field
+ * is the SSID and which field is the flags.
+ */
+ String ssid;
+ String flags;
+ if (result.length == 4) {
+ if (result[3].charAt(0) == '[') {
+ flags = result[3];
+ ssid = "";
+ } else {
+ flags = "";
+ ssid = result[3];
+ }
+ } else if (result.length == 5) {
+ flags = result[3];
+ ssid = result[4];
+ } else {
+ // Here, we must have 3 fields: no flags and ssid
+ // set
+ flags = "";
+ ssid = "";
+ }
+
+ // Do not add scan results that have no SSID set
+ if (0 < ssid.trim().length()) {
+ scanResult =
+ new ScanResult(
+ ssid, result[0], flags, level, frequency);
+ mScanResultCache.put(line, scanResult);
+ }
+ } else {
+ Log.w(TAG, "Misformatted scan result text with " +
+ result.length + " fields: " + line);
+ }
+ }
+ }
+ }
+
+ return scanResult;
+ }
+
+ /**
+ * Parse the "flags" field passed back in a scan result by wpa_supplicant,
+ * and construct a {@code WifiConfiguration} that describes the encryption,
+ * key management, and authenticaion capabilities of the access point.
+ * @param flags the string returned by wpa_supplicant
+ * @return the {@link WifiConfiguration} object, filled in
+ */
+ WifiConfiguration parseScanFlags(String flags) {
+ WifiConfiguration config = new WifiConfiguration();
+
+ if (flags.length() == 0) {
+ config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ }
+ // ... to be implemented
+ return config;
+ }
+
+ /**
+ * Tell the supplicant to persist the current list of configured networks.
+ * @return {@code true} if the operation succeeded
+ */
+ public boolean saveConfiguration() {
+ boolean result;
+ enforceChangePermission();
+ synchronized (mWifiStateTracker) {
+ result = WifiNative.saveConfigCommand();
+ if (result && mNeedReconfig) {
+ mNeedReconfig = false;
+ result = WifiNative.reloadConfigCommand();
+
+ if (result) {
+ Intent intent = new Intent(WifiManager.NETWORK_IDS_CHANGED_ACTION);
+ mContext.sendBroadcast(intent);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Set the number of radio frequency channels that are allowed to be used
+ * in the current regulatory domain. This method should be used only
+ * if the correct number of channels cannot be determined automatically
+ * for some reason. If the operation is successful, the new value is
+ * persisted as a Secure setting.
+ * @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 boolean setNumAllowedChannels(int numChannels) {
+ enforceChangePermission();
+ /*
+ * Validate the argument. We'd like to let the Wi-Fi driver do this,
+ * but if Wi-Fi isn't currently enabled, that's not possible, and
+ * we want to persist the setting anyway,so that it will take
+ * effect when Wi-Fi does become enabled.
+ */
+ boolean found = false;
+ for (int validChan : sValidRegulatoryChannelCounts) {
+ if (validChan == numChannels) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
+ numChannels);
+ mWifiStateTracker.setNumAllowedChannels(numChannels);
+ return true;
+ }
+
+ /**
+ * Return the number of frequency channels that are allowed
+ * to be used in the current regulatory domain.
+ * @return the number of allowed channels, or {@code -1} if an error occurs
+ */
+ public int getNumAllowedChannels() {
+ int numChannels;
+
+ enforceAccessPermission();
+ synchronized (mWifiStateTracker) {
+ /*
+ * If we can't get the value from the driver (e.g., because
+ * Wi-Fi is not currently enabled), get the value from
+ * Settings.
+ */
+ numChannels = WifiNative.getNumAllowedChannelsCommand();
+ if (numChannels < 0) {
+ numChannels = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
+ -1);
+ }
+ }
+ return numChannels;
+ }
+
+ /**
+ * Return the list of valid values for the number of allowed radio channels
+ * for various regulatory domains.
+ * @return the list of channel counts
+ */
+ public int[] getValidChannelCounts() {
+ enforceAccessPermission();
+ return sValidRegulatoryChannelCounts;
+ }
+
+ /**
+ * Return the DHCP-assigned addresses from the last successful DHCP request,
+ * if any.
+ * @return the DHCP information
+ */
+ public DhcpInfo getDhcpInfo() {
+ enforceAccessPermission();
+ return mWifiStateTracker.getDhcpInfo();
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ long idleMillis = Settings.Gservices.getLong(mContext.getContentResolver(),
+ Settings.Gservices.WIFI_IDLE_MS, DEFAULT_IDLE_MILLIS);
+ int stayAwakeConditions =
+ Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0);
+ if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ mAlarmManager.cancel(mIdleIntent);
+ mDeviceIdle = false;
+ mScreenOff = false;
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ mScreenOff = true;
+ /*
+ * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+ * AND the "stay on while plugged in" setting doesn't match the
+ * current power conditions (i.e, not plugged in, plugged in to USB,
+ * or plugged in to AC).
+ */
+ if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
+ long triggerTime = System.currentTimeMillis() + idleMillis;
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+ }
+ /* we can return now -- there's nothing to do until we get the idle intent back */
+ return;
+ } else if (action.equals(ACTION_DEVICE_IDLE)) {
+ mDeviceIdle = true;
+ } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ /*
+ * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+ * AND we are transitioning from a state in which the device was supposed
+ * to stay awake to a state in which it is not supposed to stay awake.
+ * If "stay awake" state is not changing, we do nothing, to avoid resetting
+ * the already-set timer.
+ */
+ int pluggedType = intent.getIntExtra("plugged", 0);
+ if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
+ !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
+ long triggerTime = System.currentTimeMillis() + idleMillis;
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+ mPluggedType = pluggedType;
+ return;
+ }
+ mPluggedType = pluggedType;
+ } else {
+ return;
+ }
+
+ updateWifiState();
+ }
+
+ /**
+ * Determines whether the Wi-Fi chipset should stay awake or be put to
+ * sleep. Looks at the setting for the sleep policy and the current
+ * conditions.
+ *
+ * @see #shouldDeviceStayAwake(int, int)
+ */
+ private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) {
+ int wifiSleepPolicy = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
+
+ if (wifiSleepPolicy == Settings.System.WIFI_SLEEP_POLICY_NEVER) {
+ // Never sleep
+ return true;
+ } else if ((wifiSleepPolicy == Settings.System.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
+ (pluggedType != 0)) {
+ // Never sleep while plugged, and we're plugged
+ return true;
+ } else {
+ // Default
+ return shouldDeviceStayAwake(stayAwakeConditions, pluggedType);
+ }
+ }
+
+ /**
+ * Determine whether the bit value corresponding to {@code pluggedType} is set in
+ * the bit string {@code stayAwakeConditions}. Because a {@code pluggedType} value
+ * of {@code 0} isn't really a plugged type, but rather an indication that the
+ * device isn't plugged in at all, there is no bit value corresponding to a
+ * {@code pluggedType} value of {@code 0}. That is why we shift by
+ * {@code pluggedType&nbsp;&#8212;&nbsp;1} instead of by {@code pluggedType}.
+ * @param stayAwakeConditions a bit string specifying which "plugged types" should
+ * keep the device (and hence Wi-Fi) awake.
+ * @param pluggedType the type of plug (USB, AC, or none) for which the check is
+ * being made
+ * @return {@code true} if {@code pluggedType} indicates that the device is
+ * supposed to stay awake, {@code false} otherwise.
+ */
+ private boolean shouldDeviceStayAwake(int stayAwakeConditions, int pluggedType) {
+ return (stayAwakeConditions & pluggedType) != 0;
+ }
+ };
+
+ private void sendEnableMessage(boolean enable, boolean persist) {
+ Message msg = Message.obtain(mWifiHandler,
+ (enable ? MESSAGE_ENABLE_WIFI : MESSAGE_DISABLE_WIFI),
+ (persist ? 1 : 0), 0);
+ msg.sendToTarget();
+ }
+
+ private void sendStartMessage(boolean scanOnlyMode) {
+ Message.obtain(mWifiHandler, MESSAGE_START_WIFI, scanOnlyMode ? 1 : 0, 0).sendToTarget();
+ }
+
+ private void updateWifiState() {
+ boolean wifiEnabled = getPersistedWifiEnabled();
+ boolean airplaneMode = isAirplaneModeOn();
+ boolean lockHeld = mLocks.hasLocks();
+ int strongestLockMode;
+ boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode;
+ boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld;
+ if (mDeviceIdle && lockHeld) {
+ strongestLockMode = mLocks.getStrongestLockMode();
+ } else {
+ strongestLockMode = WifiManager.WIFI_MODE_FULL;
+ }
+
+ synchronized (mWifiHandler) {
+ if (mWifiState == WIFI_STATE_ENABLING && !airplaneMode) {
+ return;
+ }
+ if (wifiShouldBeEnabled) {
+ if (wifiShouldBeStarted) {
+ sWakeLock.acquire();
+ sendEnableMessage(true, false);
+ sWakeLock.acquire();
+ sendStartMessage(strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
+ } else {
+ int wakeLockTimeout =
+ Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
+ DEFAULT_WAKELOCK_TIMEOUT);
+ /*
+ * The following wakelock is held in order to ensure
+ * that the connectivity manager has time to fail over
+ * to the mobile data network. The connectivity manager
+ * releases it once mobile data connectivity has been
+ * established. If connectivity cannot be established,
+ * the wakelock is released after wakeLockTimeout
+ * milliseconds have elapsed.
+ */
+ sDriverStopWakeLock.acquire();
+ mWifiHandler.sendEmptyMessage(MESSAGE_STOP_WIFI);
+ mWifiHandler.sendEmptyMessageDelayed(MESSAGE_RELEASE_WAKELOCK, wakeLockTimeout);
+ }
+ } else {
+ sWakeLock.acquire();
+ sendEnableMessage(false, false);
+ }
+ }
+ }
+
+ private void registerForBroadcasts() {
+ IntentFilter intentFilter = new IntentFilter();
+ if (isAirplaneSensitive()) {
+ intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ }
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ intentFilter.addAction(ACTION_DEVICE_IDLE);
+ mContext.registerReceiver(mReceiver, intentFilter);
+ }
+
+ private boolean isAirplaneSensitive() {
+ String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
+ Settings.System.AIRPLANE_MODE_RADIOS);
+ return airplaneModeRadios == null
+ || airplaneModeRadios.contains(Settings.System.RADIO_WIFI);
+ }
+
+ /**
+ * Returns true if Wi-Fi is sensitive to airplane mode, and airplane mode is
+ * currently on.
+ * @return {@code true} if airplane mode is on.
+ */
+ private boolean isAirplaneModeOn() {
+ return isAirplaneSensitive() && Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ /**
+ * Handler that allows posting to the WifiThread.
+ */
+ private class WifiHandler extends Handler {
+ public WifiHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+
+ case MESSAGE_ENABLE_WIFI:
+ setWifiEnabledBlocking(true, msg.arg1 == 1);
+ sWakeLock.release();
+ break;
+
+ case MESSAGE_START_WIFI:
+ mWifiStateTracker.setScanOnlyMode(msg.arg1 != 0);
+ mWifiStateTracker.restart();
+ sWakeLock.release();
+ break;
+
+ case MESSAGE_DISABLE_WIFI:
+ // a non-zero msg.arg1 value means the "enabled" setting
+ // should be persisted
+ setWifiEnabledBlocking(false, msg.arg1 == 1);
+ sWakeLock.release();
+ break;
+
+ case MESSAGE_STOP_WIFI:
+ mWifiStateTracker.disconnectAndStop();
+ // don't release wakelock
+ break;
+
+ case MESSAGE_RELEASE_WAKELOCK:
+ synchronized (sDriverStopWakeLock) {
+ if (sDriverStopWakeLock.isHeld()) {
+ sDriverStopWakeLock.release();
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump WifiService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ pw.println("Wi-Fi is " + stateName(mWifiState));
+ pw.println("Stay-awake conditions: " +
+ Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0));
+ pw.println();
+
+ pw.println("Internal state:");
+ pw.println(mWifiStateTracker);
+ pw.println();
+ pw.println("Latest scan results:");
+ List<ScanResult> scanResults = mWifiStateTracker.getScanResultsList();
+ if (scanResults != null && scanResults.size() != 0) {
+ pw.println(" BSSID Frequency RSSI Flags SSID");
+ for (ScanResult r : scanResults) {
+ pw.printf(" %17s %9d %5d %-16s %s%n",
+ r.BSSID,
+ r.frequency,
+ r.level,
+ r.capabilities,
+ r.SSID == null ? "" : r.SSID);
+ }
+ }
+ pw.println();
+ pw.println("Locks held:");
+ mLocks.dump(pw);
+ }
+
+ private static String stateName(int wifiState) {
+ switch (wifiState) {
+ case WIFI_STATE_DISABLING:
+ return "disabling";
+ case WIFI_STATE_DISABLED:
+ return "disabled";
+ case WIFI_STATE_ENABLING:
+ return "enabling";
+ case WIFI_STATE_ENABLED:
+ return "enabled";
+ case WIFI_STATE_UNKNOWN:
+ return "unknown state";
+ default:
+ return "[invalid state]";
+ }
+ }
+
+ private class WifiLock implements IBinder.DeathRecipient {
+ String mTag;
+ int mLockMode;
+ IBinder mBinder;
+
+ WifiLock(int lockMode, String tag, IBinder binder) {
+ super();
+ mTag = tag;
+ mLockMode = lockMode;
+ mBinder = binder;
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ public void binderDied() {
+ synchronized (mLocks) {
+ releaseWifiLockLocked(mBinder);
+ }
+ }
+
+ public String toString() {
+ return "WifiLock{" + mTag + " type=" + mLockMode + " binder=" + mBinder + "}";
+ }
+ }
+
+ private class LockList {
+ private List<WifiLock> mList;
+
+ private LockList() {
+ mList = new ArrayList<WifiLock>();
+ }
+
+ private synchronized boolean hasLocks() {
+ return !mList.isEmpty();
+ }
+
+ private synchronized int getStrongestLockMode() {
+ if (mList.isEmpty()) {
+ return WifiManager.WIFI_MODE_FULL;
+ }
+ for (WifiLock l : mList) {
+ if (l.mLockMode == WifiManager.WIFI_MODE_FULL) {
+ return WifiManager.WIFI_MODE_FULL;
+ }
+ }
+ return WifiManager.WIFI_MODE_SCAN_ONLY;
+ }
+
+ private void addLock(WifiLock lock) {
+ if (findLockByBinder(lock.mBinder) < 0) {
+ mList.add(lock);
+ }
+ }
+
+ private WifiLock removeLock(IBinder binder) {
+ int index = findLockByBinder(binder);
+ if (index >= 0) {
+ return mList.remove(index);
+ } else {
+ return null;
+ }
+ }
+
+ private int findLockByBinder(IBinder binder) {
+ int size = mList.size();
+ for (int i = size - 1; i >= 0; i--)
+ if (mList.get(i).mBinder == binder)
+ return i;
+ return -1;
+ }
+
+ private void dump(PrintWriter pw) {
+ for (WifiLock l : mList) {
+ pw.print(" ");
+ pw.println(l);
+ }
+ }
+ }
+
+ public boolean acquireWifiLock(IBinder binder, int lockMode, String tag) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+ if (lockMode != WifiManager.WIFI_MODE_FULL && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY) {
+ return false;
+ }
+ WifiLock wifiLock = new WifiLock(lockMode, tag, binder);
+ synchronized (mLocks) {
+ return acquireWifiLockLocked(wifiLock);
+ }
+ }
+
+ private boolean acquireWifiLockLocked(WifiLock wifiLock) {
+ mLocks.addLock(wifiLock);
+ updateWifiState();
+ return true;
+ }
+
+ public boolean releaseWifiLock(IBinder lock) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+ synchronized (mLocks) {
+ return releaseWifiLockLocked(lock);
+ }
+ }
+
+ private boolean releaseWifiLockLocked(IBinder lock) {
+ boolean result;
+ result = (mLocks.removeLock(lock) != null);
+ updateWifiState();
+ return result;
+ }
+}