summaryrefslogtreecommitdiffstats
path: root/packages/SettingsLib/src/com
diff options
context:
space:
mode:
authorJason Monk <jmonk@google.com>2015-01-28 10:40:41 -0500
committerJason Monk <jmonk@google.com>2015-02-04 15:08:55 -0500
commitd52356aa5e82c7c5db61672bbe8d0f44861f3e59 (patch)
treeb2ccbf3e88c9bacba4921e81b037514156f64c4c /packages/SettingsLib/src/com
parent1a81b83e1ef3ccf13cf32bb621537a6bda5b33f7 (diff)
downloadframeworks_base-d52356aa5e82c7c5db61672bbe8d0f44861f3e59.zip
frameworks_base-d52356aa5e82c7c5db61672bbe8d0f44861f3e59.tar.gz
frameworks_base-d52356aa5e82c7c5db61672bbe8d0f44861f3e59.tar.bz2
Move Settings wifi tracking to SettingsLib
Make SettingsLib capable of tracking which wifi networks are available/saved and their state. Also modify Quick Settings to use this code rather than having its own logic. Bug: 19180466 Change-Id: Iff9f9aed240d79323dba41496496e8076b9fa6f2
Diffstat (limited to 'packages/SettingsLib/src/com')
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Blank.java0
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/WirelessUtils.java37
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java739
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java465
4 files changed, 1241 insertions, 0 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/Blank.java b/packages/SettingsLib/src/com/android/settingslib/Blank.java
deleted file mode 100644
index e69de29..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/Blank.java
+++ /dev/null
diff --git a/packages/SettingsLib/src/com/android/settingslib/WirelessUtils.java b/packages/SettingsLib/src/com/android/settingslib/WirelessUtils.java
new file mode 100644
index 0000000..0346a77
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/WirelessUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.settingslib;
+
+import android.content.Context;
+import android.provider.Settings;
+
+public class WirelessUtils {
+
+ public static boolean isRadioAllowed(Context context, String type) {
+ if (!isAirplaneModeOn(context)) {
+ return true;
+ }
+ String toggleable = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+ return toggleable != null && toggleable.contains(type);
+ }
+
+ public static boolean isAirplaneModeOn(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
new file mode 100644
index 0000000..e8ab220
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2015 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.settingslib.wifi;
+
+import android.content.Context;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.LruCache;
+
+import com.android.settingslib.R;
+
+import java.util.Map;
+
+
+public class AccessPoint implements Comparable<AccessPoint> {
+ static final String TAG = "SettingsLib.AccessPoint";
+
+ /**
+ * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
+ */
+ public static final int LOWER_FREQ_24GHZ = 2400;
+
+ /**
+ * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
+ */
+ public static final int HIGHER_FREQ_24GHZ = 2500;
+
+ /**
+ * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
+ */
+ public static final int LOWER_FREQ_5GHZ = 4900;
+
+ /**
+ * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
+ */
+ public static final int HIGHER_FREQ_5GHZ = 5900;
+
+
+ /**
+ * Experimental: we should be able to show the user the list of BSSIDs and bands
+ * for that SSID.
+ * For now this data is used only with Verbose Logging so as to show the band and number
+ * of BSSIDs on which that network is seen.
+ */
+ public LruCache<String, ScanResult> mScanResultCache;
+ private static final String KEY_NETWORKINFO = "key_networkinfo";
+ private static final String KEY_WIFIINFO = "key_wifiinfo";
+ private static final String KEY_SCANRESULT = "key_scanresult";
+ private static final String KEY_CONFIG = "key_config";
+
+ /**
+ * These values are matched in string arrays -- changes must be kept in sync
+ */
+ public static final int SECURITY_NONE = 0;
+ public static final int SECURITY_WEP = 1;
+ public static final int SECURITY_PSK = 2;
+ public static final int SECURITY_EAP = 3;
+
+ private static final int PSK_UNKNOWN = 0;
+ private static final int PSK_WPA = 1;
+ private static final int PSK_WPA2 = 2;
+ private static final int PSK_WPA_WPA2 = 3;
+
+ private static final int VISIBILITY_OUTDATED_AGE_IN_MILLI = 20000;
+ private final Context mContext;
+
+ private String ssid;
+ private int security;
+ private int networkId = WifiConfiguration.INVALID_NETWORK_ID;
+
+ private int pskType = PSK_UNKNOWN;
+
+ private WifiConfiguration mConfig;
+ private ScanResult mScanResult;
+
+ private int mRssi = Integer.MAX_VALUE;
+ private long mSeen = 0;
+
+ private WifiInfo mInfo;
+ private NetworkInfo mNetworkInfo;
+ private AccessPointListener mAccessPointListener;
+
+ private Object mTag;
+
+ public AccessPoint(Context context, Bundle savedState) {
+ mContext = context;
+ mConfig = savedState.getParcelable(KEY_CONFIG);
+ if (mConfig != null) {
+ loadConfig(mConfig);
+ }
+ mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT);
+ if (mScanResult != null) {
+ loadResult(mScanResult);
+ }
+ mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
+ if (savedState.containsKey(KEY_NETWORKINFO)) {
+ mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
+ }
+ update(mInfo, mNetworkInfo);
+ }
+
+ AccessPoint(Context context, ScanResult result) {
+ mContext = context;
+ loadResult(result);
+ }
+
+ AccessPoint(Context context, WifiConfiguration config) {
+ mContext = context;
+ loadConfig(config);
+ }
+
+ @Override
+ public int compareTo(AccessPoint other) {
+ // Active one goes first.
+ if (isActive() && !other.isActive()) return -1;
+ if (!isActive() && other.isActive()) return 1;
+
+ // Reachable one goes before unreachable one.
+ if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
+ if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
+
+ // Configured one goes before unconfigured one.
+ if (networkId != WifiConfiguration.INVALID_NETWORK_ID
+ && other.networkId == WifiConfiguration.INVALID_NETWORK_ID) return -1;
+ if (networkId == WifiConfiguration.INVALID_NETWORK_ID
+ && other.networkId != WifiConfiguration.INVALID_NETWORK_ID) return 1;
+
+ // Sort by signal strength.
+ int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi);
+ if (difference != 0) {
+ return difference;
+ }
+ // Sort by ssid.
+ return ssid.compareToIgnoreCase(other.ssid);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof AccessPoint)) return false;
+ return (this.compareTo((AccessPoint) other) == 0);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ if (mInfo != null) result += 13 * mInfo.hashCode();
+ result += 19 * mRssi;
+ result += 23 * networkId;
+ result += 29 * ssid.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder().append("AccessPoint(")
+ .append(ssid);
+ if (isSaved()) {
+ builder.append(',').append("saved");
+ }
+ if (isActive()) {
+ builder.append(',').append("active");
+ }
+ if (isEphemeral()) {
+ builder.append(',').append("ephemeral");
+ }
+ if (isConnectable()) {
+ builder.append(',').append("connectable");
+ }
+ if (security != SECURITY_NONE) {
+ builder.append(',').append(securityToString(security, pskType));
+ }
+ return builder.append(')').toString();
+ }
+
+ public boolean matches(ScanResult result) {
+ return ssid.equals(result.SSID) && security == getSecurity(result);
+ }
+
+ public boolean matches(WifiConfiguration config) {
+ return ssid.equals(removeDoubleQuotes(config.SSID)) && security == getSecurity(config);
+ }
+
+ public WifiConfiguration getConfig() {
+ return mConfig;
+ }
+
+ public void clearConfig() {
+ mConfig = null;
+ networkId = WifiConfiguration.INVALID_NETWORK_ID;
+ }
+
+ public WifiInfo getInfo() {
+ return mInfo;
+ }
+
+ public int getLevel() {
+ if (mRssi == Integer.MAX_VALUE) {
+ return -1;
+ }
+ return WifiManager.calculateSignalLevel(mRssi, 4);
+ }
+
+ public NetworkInfo getNetworkInfo() {
+ return mNetworkInfo;
+ }
+
+ public int getSecurity() {
+ return security;
+ }
+
+ public String getSecurityString(boolean concise) {
+ Context context = mContext;
+ switch(security) {
+ case SECURITY_EAP:
+ return concise ? context.getString(R.string.wifi_security_short_eap) :
+ context.getString(R.string.wifi_security_eap);
+ case SECURITY_PSK:
+ switch (pskType) {
+ case PSK_WPA:
+ return concise ? context.getString(R.string.wifi_security_short_wpa) :
+ context.getString(R.string.wifi_security_wpa);
+ case PSK_WPA2:
+ return concise ? context.getString(R.string.wifi_security_short_wpa2) :
+ context.getString(R.string.wifi_security_wpa2);
+ case PSK_WPA_WPA2:
+ return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
+ context.getString(R.string.wifi_security_wpa_wpa2);
+ case PSK_UNKNOWN:
+ default:
+ return concise ? context.getString(R.string.wifi_security_short_psk_generic)
+ : context.getString(R.string.wifi_security_psk_generic);
+ }
+ case SECURITY_WEP:
+ return concise ? context.getString(R.string.wifi_security_short_wep) :
+ context.getString(R.string.wifi_security_wep);
+ case SECURITY_NONE:
+ default:
+ return concise ? "" : context.getString(R.string.wifi_security_none);
+ }
+ }
+
+ public String getSsid() {
+ return ssid;
+ }
+
+ public DetailedState getDetailedState() {
+ return mNetworkInfo != null ? mNetworkInfo.getDetailedState() : null;
+ }
+
+ public String getSummary() {
+ // Update to new summary
+ StringBuilder summary = new StringBuilder();
+
+ if (isActive()) { // This is the active connection
+ summary.append(getSummary(mContext, getDetailedState(),
+ networkId == WifiConfiguration.INVALID_NETWORK_ID));
+ } else if (mConfig != null
+ && mConfig.hasNoInternetAccess()) {
+ summary.append(mContext.getString(R.string.wifi_no_internet));
+ } else if (mConfig != null && ((mConfig.status == WifiConfiguration.Status.DISABLED &&
+ mConfig.disableReason != WifiConfiguration.DISABLED_UNKNOWN_REASON)
+ || mConfig.autoJoinStatus
+ >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE)) {
+ if (mConfig.autoJoinStatus
+ >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
+ if (mConfig.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE) {
+ summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
+ } else if (mConfig.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE) {
+ summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
+ } else {
+ summary.append(mContext.getString(R.string.wifi_disabled_wifi_failure));
+ }
+ } else {
+ switch (mConfig.disableReason) {
+ case WifiConfiguration.DISABLED_AUTH_FAILURE:
+ summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
+ break;
+ case WifiConfiguration.DISABLED_DHCP_FAILURE:
+ case WifiConfiguration.DISABLED_DNS_FAILURE:
+ summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
+ break;
+ case WifiConfiguration.DISABLED_UNKNOWN_REASON:
+ case WifiConfiguration.DISABLED_ASSOCIATION_REJECT:
+ summary.append(mContext.getString(R.string.wifi_disabled_generic));
+ break;
+ }
+ }
+ } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
+ summary.append(mContext.getString(R.string.wifi_not_in_range));
+ } else { // In range, not disabled.
+ if (mConfig != null) { // Is saved network
+ summary.append(mContext.getString(R.string.wifi_remembered));
+ }
+ }
+
+ if (WifiTracker.sVerboseLogging > 0) {
+ // Add RSSI/band information for this config, what was seen up to 6 seconds ago
+ // verbose WiFi Logging is only turned on thru developers settings
+ if (mInfo != null && mNetworkInfo != null) { // This is the active connection
+ summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
+ }
+ summary.append(" " + getVisibilityStatus());
+ if (mConfig != null && mConfig.autoJoinStatus > 0) {
+ summary.append(" (" + mConfig.autoJoinStatus);
+ if (mConfig.blackListTimestamp > 0) {
+ long now = System.currentTimeMillis();
+ long diff = (now - mConfig.blackListTimestamp)/1000;
+ long sec = diff%60; //seconds
+ long min = (diff/60)%60; //minutes
+ long hour = (min/60)%60; //hours
+ summary.append(", ");
+ if (hour > 0) summary.append(Long.toString(hour) + "h ");
+ summary.append( Long.toString(min) + "m ");
+ summary.append( Long.toString(sec) + "s ");
+ }
+ summary.append(")");
+ }
+ if (mConfig != null && mConfig.numIpConfigFailures > 0) {
+ summary.append(" ipf=").append(mConfig.numIpConfigFailures);
+ }
+ if (mConfig != null && mConfig.numConnectionFailures > 0) {
+ summary.append(" cf=").append(mConfig.numConnectionFailures);
+ }
+ if (mConfig != null && mConfig.numAuthFailures > 0) {
+ summary.append(" authf=").append(mConfig.numAuthFailures);
+ }
+ if (mConfig != null && mConfig.numNoInternetAccessReports > 0) {
+ summary.append(" noInt=").append(mConfig.numNoInternetAccessReports);
+ }
+ }
+ return summary.toString();
+ }
+
+ /**
+ * Returns the visibility status of the WifiConfiguration.
+ *
+ * @return autojoin debugging information
+ * TODO: use a string formatter
+ * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
+ * For instance [-40,5/-30,2]
+ */
+ private String getVisibilityStatus() {
+ StringBuilder visibility = new StringBuilder();
+ StringBuilder scans24GHz = null;
+ StringBuilder scans5GHz = null;
+ String bssid = null;
+
+ long now = System.currentTimeMillis();
+
+ if (mInfo != null) {
+ bssid = mInfo.getBSSID();
+ if (bssid != null) {
+ visibility.append(" ").append(bssid);
+ }
+ visibility.append(" rssi=").append(mInfo.getRssi());
+ visibility.append(" ");
+ visibility.append(" score=").append(mInfo.score);
+ visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
+ visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
+ visibility.append(String.format("%.1f ", mInfo.txBadRate));
+ visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
+ }
+
+ if (mScanResultCache != null) {
+ int rssi5 = WifiConfiguration.INVALID_RSSI;
+ int rssi24 = WifiConfiguration.INVALID_RSSI;
+ int num5 = 0;
+ int num24 = 0;
+ int numBlackListed = 0;
+ int n24 = 0; // Number scan results we included in the string
+ int n5 = 0; // Number scan results we included in the string
+ Map<String, ScanResult> list = mScanResultCache.snapshot();
+ // TODO: sort list by RSSI or age
+ for (ScanResult result : list.values()) {
+ if (result.seen == 0)
+ continue;
+
+ if (result.autoJoinStatus != ScanResult.ENABLED) numBlackListed++;
+
+ if (result.frequency >= LOWER_FREQ_5GHZ
+ && result.frequency <= HIGHER_FREQ_5GHZ) {
+ // Strictly speaking: [4915, 5825]
+ // number of known BSSID on 5GHz band
+ num5 = num5 + 1;
+ } else if (result.frequency >= LOWER_FREQ_24GHZ
+ && result.frequency <= HIGHER_FREQ_24GHZ) {
+ // Strictly speaking: [2412, 2482]
+ // number of known BSSID on 2.4Ghz band
+ num24 = num24 + 1;
+ }
+
+ // Ignore results seen, older than 20 seconds
+ if (now - result.seen > VISIBILITY_OUTDATED_AGE_IN_MILLI) continue;
+
+ if (result.frequency >= LOWER_FREQ_5GHZ
+ && result.frequency <= HIGHER_FREQ_5GHZ) {
+ if (result.level > rssi5) {
+ rssi5 = result.level;
+ }
+ if (n5 < 4) {
+ if (scans5GHz == null) scans5GHz = new StringBuilder();
+ scans5GHz.append(" \n{").append(result.BSSID);
+ if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*");
+ scans5GHz.append("=").append(result.frequency);
+ scans5GHz.append(",").append(result.level);
+ if (result.autoJoinStatus != 0) {
+ scans5GHz.append(",st=").append(result.autoJoinStatus);
+ }
+ if (result.numIpConfigFailures != 0) {
+ scans5GHz.append(",ipf=").append(result.numIpConfigFailures);
+ }
+ scans5GHz.append("}");
+ n5++;
+ }
+ } else if (result.frequency >= LOWER_FREQ_24GHZ
+ && result.frequency <= HIGHER_FREQ_24GHZ) {
+ if (result.level > rssi24) {
+ rssi24 = result.level;
+ }
+ if (n24 < 4) {
+ if (scans24GHz == null) scans24GHz = new StringBuilder();
+ scans24GHz.append(" \n{").append(result.BSSID);
+ if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*");
+ scans24GHz.append("=").append(result.frequency);
+ scans24GHz.append(",").append(result.level);
+ if (result.autoJoinStatus != 0) {
+ scans24GHz.append(",st=").append(result.autoJoinStatus);
+ }
+ if (result.numIpConfigFailures != 0) {
+ scans24GHz.append(",ipf=").append(result.numIpConfigFailures);
+ }
+ scans24GHz.append("}");
+ n24++;
+ }
+ }
+ }
+ visibility.append(" [");
+ if (num24 > 0) {
+ visibility.append("(").append(num24).append(")");
+ if (n24 <= 4) {
+ if (scans24GHz != null) {
+ visibility.append(scans24GHz.toString());
+ }
+ } else {
+ visibility.append("max=").append(rssi24);
+ if (scans24GHz != null) {
+ visibility.append(",").append(scans24GHz.toString());
+ }
+ }
+ }
+ visibility.append(";");
+ if (num5 > 0) {
+ visibility.append("(").append(num5).append(")");
+ if (n5 <= 4) {
+ if (scans5GHz != null) {
+ visibility.append(scans5GHz.toString());
+ }
+ } else {
+ visibility.append("max=").append(rssi5);
+ if (scans5GHz != null) {
+ visibility.append(",").append(scans5GHz.toString());
+ }
+ }
+ }
+ if (numBlackListed > 0)
+ visibility.append("!").append(numBlackListed);
+ visibility.append("]");
+ } else {
+ if (mRssi != Integer.MAX_VALUE) {
+ visibility.append(" rssi=");
+ visibility.append(mRssi);
+ if (mScanResult != null) {
+ visibility.append(", f=");
+ visibility.append(mScanResult.frequency);
+ }
+ }
+ }
+
+ return visibility.toString();
+ }
+
+ /**
+ * Return whether this is the active connection.
+ * For ephemeral connections (networkId is invalid), this returns false if the network is
+ * disconnected.
+ */
+ public boolean isActive() {
+ return mNetworkInfo != null &&
+ (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
+ mNetworkInfo.getState() != State.DISCONNECTED);
+ }
+
+ public boolean isConnectable() {
+ return getLevel() != -1 && getDetailedState() == null;
+ }
+
+ public boolean isEphemeral() {
+ return !isSaved() && mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED;
+ }
+
+ /** Return whether the given {@link WifiInfo} is for this access point. */
+ private boolean isInfoForThisAccessPoint(WifiInfo info) {
+ if (networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+ return networkId == info.getNetworkId();
+ } else {
+ // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
+ // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
+ // TODO: Handle hex string SSIDs.
+ return ssid.equals(removeDoubleQuotes(info.getSSID()));
+ }
+ }
+
+ public boolean isSaved() {
+ return networkId != WifiConfiguration.INVALID_NETWORK_ID;
+ }
+
+ public Object getTag() {
+ return mTag;
+ }
+
+ public void setTag(Object tag) {
+ mTag = tag;
+ }
+
+ /**
+ * Generate and save a default wifiConfiguration with common values.
+ * Can only be called for unsecured networks.
+ */
+ public void generateOpenNetworkConfig() {
+ if (security != SECURITY_NONE)
+ throw new IllegalStateException();
+ if (mConfig != null)
+ return;
+ mConfig = new WifiConfiguration();
+ mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
+ mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
+ }
+
+ void loadConfig(WifiConfiguration config) {
+ ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
+ security = getSecurity(config);
+ networkId = config.networkId;
+ mConfig = config;
+ }
+
+ private void loadResult(ScanResult result) {
+ ssid = result.SSID;
+ security = getSecurity(result);
+ if (security == SECURITY_PSK)
+ pskType = getPskType(result);
+ mRssi = result.level;
+ mScanResult = result;
+ if (result.seen > mSeen) {
+ mSeen = result.seen;
+ }
+ }
+
+ public void saveWifiState(Bundle savedState) {
+ savedState.putParcelable(KEY_CONFIG, mConfig);
+ savedState.putParcelable(KEY_SCANRESULT, mScanResult);
+ savedState.putParcelable(KEY_WIFIINFO, mInfo);
+ if (mNetworkInfo != null) {
+ savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
+ }
+ }
+
+ public void setListener(AccessPointListener listener) {
+ mAccessPointListener = listener;
+ }
+
+ boolean update(ScanResult result) {
+ if (result.seen > mSeen) {
+ mSeen = result.seen;
+ }
+ if (WifiTracker.sVerboseLogging > 0) {
+ if (mScanResultCache == null) {
+ mScanResultCache = new LruCache<String, ScanResult>(32);
+ }
+ mScanResultCache.put(result.BSSID, result);
+ }
+
+ if (ssid.equals(result.SSID) && security == getSecurity(result)) {
+ if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) {
+ int oldLevel = getLevel();
+ mRssi = result.level;
+ if (getLevel() != oldLevel && mAccessPointListener != null) {
+ mAccessPointListener.onLevelChanged(this);
+ }
+ }
+ // This flag only comes from scans, is not easily saved in config
+ if (security == SECURITY_PSK) {
+ pskType = getPskType(result);
+ }
+ mScanResult = result;
+ if (mAccessPointListener != null) {
+ mAccessPointListener.onAccessPointChanged(this);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean update(WifiInfo info, NetworkInfo networkInfo) {
+ boolean reorder = false;
+ if (info != null && isInfoForThisAccessPoint(info)) {
+ reorder = (mInfo == null);
+ mRssi = info.getRssi();
+ mInfo = info;
+ mNetworkInfo = networkInfo;
+ if (mAccessPointListener != null) {
+ mAccessPointListener.onAccessPointChanged(this);
+ }
+ } else if (mInfo != null) {
+ reorder = true;
+ mInfo = null;
+ mNetworkInfo = null;
+ if (mAccessPointListener != null) {
+ mAccessPointListener.onAccessPointChanged(this);
+ }
+ }
+ return reorder;
+ }
+
+ public static String getSummary(Context context, String ssid, DetailedState state,
+ boolean isEphemeral) {
+ if (state == DetailedState.CONNECTED && isEphemeral && ssid == null) {
+ // Special case for connected + ephemeral networks.
+ return context.getString(R.string.connected_via_wfa);
+ }
+
+ String[] formats = context.getResources().getStringArray((ssid == null)
+ ? R.array.wifi_status : R.array.wifi_status_with_ssid);
+ int index = state.ordinal();
+
+ if (index >= formats.length || formats[index].length() == 0) {
+ return null;
+ }
+ return String.format(formats[index], ssid);
+ }
+
+ public static String getSummary(Context context, DetailedState state, boolean isEphemeral) {
+ return getSummary(context, null, state, isEphemeral);
+ }
+
+ public static String convertToQuotedString(String string) {
+ return "\"" + string + "\"";
+ }
+
+ private static int getPskType(ScanResult result) {
+ boolean wpa = result.capabilities.contains("WPA-PSK");
+ boolean wpa2 = result.capabilities.contains("WPA2-PSK");
+ if (wpa2 && wpa) {
+ return PSK_WPA_WPA2;
+ } else if (wpa2) {
+ return PSK_WPA2;
+ } else if (wpa) {
+ return PSK_WPA;
+ } else {
+ Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
+ return PSK_UNKNOWN;
+ }
+ }
+
+ private static int getSecurity(ScanResult result) {
+ if (result.capabilities.contains("WEP")) {
+ return SECURITY_WEP;
+ } else if (result.capabilities.contains("PSK")) {
+ return SECURITY_PSK;
+ } else if (result.capabilities.contains("EAP")) {
+ return SECURITY_EAP;
+ }
+ return SECURITY_NONE;
+ }
+
+ static int getSecurity(WifiConfiguration config) {
+ if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+ return SECURITY_PSK;
+ }
+ if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
+ config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+ return SECURITY_EAP;
+ }
+ return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
+ }
+
+ public static String securityToString(int security, int pskType) {
+ if (security == SECURITY_WEP) {
+ return "WEP";
+ } else if (security == SECURITY_PSK) {
+ if (pskType == PSK_WPA) {
+ return "WPA";
+ } else if (pskType == PSK_WPA2) {
+ return "WPA2";
+ } else if (pskType == PSK_WPA_WPA2) {
+ return "WPA_WPA2";
+ }
+ return "PSK";
+ } else if (security == SECURITY_EAP) {
+ return "EAP";
+ }
+ return "NONE";
+ }
+
+ static String removeDoubleQuotes(String string) {
+ int length = string.length();
+ if ((length > 1) && (string.charAt(0) == '"')
+ && (string.charAt(length - 1) == '"')) {
+ return string.substring(1, length - 1);
+ }
+ return string;
+ }
+
+ public interface AccessPointListener {
+ void onAccessPointChanged(AccessPoint accessPoint);
+ void onLevelChanged(AccessPoint accessPoint);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
new file mode 100644
index 0000000..c3e23d2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2015 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.settingslib.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Message;
+import android.widget.Toast;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.R;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tracks saved or available wifi networks and their state.
+ */
+public class WifiTracker {
+ private static final String TAG = "WifiTracker";
+
+ /** verbose logging flag. this flag is set thru developer debugging options
+ * and used so as to assist with in-the-field WiFi connectivity debugging */
+ public static int sVerboseLogging = 0;
+
+ // TODO: Allow control of this?
+ // Combo scans can take 5-6s to complete - set to 10s.
+ private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
+
+ private final Context mContext;
+ private final WifiManager mWifiManager;
+ private final IntentFilter mFilter;
+
+ private final AtomicBoolean mConnected = new AtomicBoolean(false);
+ private final WifiListener mListener;
+ private final boolean mIncludeSaved;
+ private final boolean mIncludeScans;
+
+ private boolean mSavedNetworksExist;
+ private boolean mRegistered;
+ private ArrayList<AccessPoint> mAccessPoints = new ArrayList<>();
+ private ArrayList<AccessPoint> mCachedAccessPoints = new ArrayList<>();
+
+ private NetworkInfo mLastNetworkInfo;
+ private WifiInfo mLastInfo;
+
+ @VisibleForTesting
+ Scanner mScanner;
+
+ public WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved,
+ boolean includeScans) {
+ this(context, wifiListener, includeSaved, includeScans,
+ (WifiManager) context.getSystemService(Context.WIFI_SERVICE));
+ }
+
+ @VisibleForTesting
+ WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved,
+ boolean includeScans, WifiManager wifiManager) {
+ if (!includeSaved && !includeScans) {
+ throw new IllegalArgumentException("Must include either saved or scans");
+ }
+ mContext = context;
+ mWifiManager = wifiManager;
+ mIncludeSaved = includeSaved;
+ mIncludeScans = includeScans;
+ mListener = wifiListener;
+
+ // check if verbose logging has been turned on or off
+ sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
+
+ mFilter = new IntentFilter();
+ mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
+ mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+ mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
+ mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+ }
+
+ /**
+ * Forces an update of the wifi networks when not scanning.
+ */
+ public void forceUpdate() {
+ updateAccessPoints();
+ }
+
+ /**
+ * Force a scan for wifi networks to happen now.
+ */
+ public void forceScan() {
+ if (mWifiManager.isWifiEnabled() && mScanner != null) {
+ mScanner.forceScan();
+ }
+ }
+
+ /**
+ * Temporarily stop scanning for wifi networks.
+ */
+ public void pauseScanning() {
+ if (mScanner != null) {
+ mScanner.pause();
+ }
+ }
+
+ /**
+ * Resume scanning for wifi networks after it has been paused.
+ */
+ public void resumeScanning() {
+ if (mWifiManager.isWifiEnabled()) {
+ if (mScanner == null) {
+ mScanner = new Scanner();
+ }
+ mScanner.resume();
+ }
+ updateAccessPoints();
+ }
+
+ /**
+ * Start tracking wifi networks.
+ * Registers listeners and starts scanning for wifi networks. If this is not called
+ * then forceUpdate() must be called to populate getAccessPoints().
+ */
+ public void startTracking() {
+ resumeScanning();
+ if (!mRegistered) {
+ mContext.registerReceiver(mReceiver, mFilter);
+ mRegistered = true;
+ }
+ }
+
+ /**
+ * Stop tracking wifi networks.
+ * Unregisters all listeners and stops scanning for wifi networks. This should always
+ * be called when done with a WifiTracker (if startTracking was called) to ensure
+ * proper cleanup.
+ */
+ public void stopTracking() {
+ if (mRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mRegistered = false;
+ }
+ pauseScanning();
+ }
+
+ /**
+ * Gets the current list of access points.
+ */
+ public List<AccessPoint> getAccessPoints() {
+ return mAccessPoints;
+ }
+
+ public WifiManager getManager() {
+ return mWifiManager;
+ }
+
+ public boolean isWifiEnabled() {
+ return mWifiManager.isWifiEnabled();
+ }
+
+ /**
+ * @return true when there are saved networks on the device, regardless
+ * of whether the WifiTracker is tracking saved networks.
+ */
+ public boolean doSavedNetworksExist() {
+ return mSavedNetworksExist;
+ }
+
+ public boolean isConnected() {
+ return mConnected.get();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println(" - wifi tracker ------");
+ for (AccessPoint accessPoint : mAccessPoints) {
+ pw.println(" " + accessPoint);
+ }
+ }
+
+ private void updateAccessPoints() {
+ // Swap the current access points into a cached list.
+ ArrayList<AccessPoint> tmpSwp = mAccessPoints;
+ mAccessPoints = mCachedAccessPoints;
+ mCachedAccessPoints = tmpSwp;
+ // Clear out the configs so we don't think something is saved when it isn't.
+ for (AccessPoint accessPoint : mCachedAccessPoints) {
+ accessPoint.clearConfig();
+ }
+
+ mAccessPoints.clear();
+
+ /** Lookup table to more quickly update AccessPoints by only considering objects with the
+ * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
+ Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
+
+ final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+ if (configs != null) {
+ mSavedNetworksExist = configs.size() != 0;
+ for (WifiConfiguration config : configs) {
+ if (config.selfAdded && config.numAssociation == 0) {
+ continue;
+ }
+ AccessPoint accessPoint = getCachedOrCreate(config);
+ if (mLastInfo != null && mLastNetworkInfo != null) {
+ accessPoint.update(mLastInfo, mLastNetworkInfo);
+ }
+ if (mIncludeSaved) {
+ mAccessPoints.add(accessPoint);
+ apMap.put(accessPoint.getSsid(), accessPoint);
+ } else {
+ // If we aren't using saved networks, drop them into the cache so that
+ // we have access to their saved info.
+ mCachedAccessPoints.add(accessPoint);
+ }
+ }
+ }
+
+ final List<ScanResult> results = mWifiManager.getScanResults();
+ if (results != null) {
+ for (ScanResult result : results) {
+ // Ignore hidden and ad-hoc networks.
+ if (result.SSID == null || result.SSID.length() == 0 ||
+ result.capabilities.contains("[IBSS]")) {
+ continue;
+ }
+
+ boolean found = false;
+ for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
+ if (accessPoint.update(result)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found && mIncludeScans) {
+ AccessPoint accessPoint = getCachedOrCreate(result);
+ if (mLastInfo != null && mLastNetworkInfo != null) {
+ accessPoint.update(mLastInfo, mLastNetworkInfo);
+ }
+ mAccessPoints.add(accessPoint);
+ apMap.put(accessPoint.getSsid(), accessPoint);
+ }
+ }
+ }
+
+ // Pre-sort accessPoints to speed preference insertion
+ Collections.sort(mAccessPoints);
+ if (mListener != null) {
+ mListener.onAccessPointsChanged();
+ }
+ }
+
+ private AccessPoint getCachedOrCreate(ScanResult result) {
+ final int N = mCachedAccessPoints.size();
+ for (int i = 0; i < N; i++) {
+ if (mCachedAccessPoints.get(i).matches(result)) {
+ AccessPoint ret = mCachedAccessPoints.remove(i);
+ ret.update(result);
+ return ret;
+ }
+ }
+ return new AccessPoint(mContext, result);
+ }
+
+ private AccessPoint getCachedOrCreate(WifiConfiguration config) {
+ final int N = mCachedAccessPoints.size();
+ for (int i = 0; i < N; i++) {
+ if (mCachedAccessPoints.get(i).matches(config)) {
+ AccessPoint ret = mCachedAccessPoints.remove(i);
+ ret.loadConfig(config);
+ return ret;
+ }
+ }
+ return new AccessPoint(mContext, config);
+ }
+
+ private void updateNetworkInfo(NetworkInfo networkInfo) {
+ /* sticky broadcasts can call this when wifi is disabled */
+ if (!mWifiManager.isWifiEnabled()) {
+ mScanner.pause();
+ return;
+ }
+
+ if (networkInfo != null &&
+ networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
+ mScanner.pause();
+ } else {
+ mScanner.resume();
+ }
+
+ mLastInfo = mWifiManager.getConnectionInfo();
+ if (networkInfo != null) {
+ mLastNetworkInfo = networkInfo;
+ }
+
+ boolean reorder = false;
+ for (int i = mAccessPoints.size() - 1; i >= 0; --i) {
+ if (mAccessPoints.get(i).update(mLastInfo, mLastNetworkInfo)) {
+ reorder = true;
+ }
+ }
+ if (reorder) {
+ Collections.sort(mAccessPoints);
+ if (mListener != null) {
+ mListener.onAccessPointsChanged();
+ }
+ }
+ }
+
+ private void updateWifiState(int state) {
+ if (state == WifiManager.WIFI_STATE_ENABLED) {
+ mScanner.resume();
+ } else {
+ mLastInfo = null;
+ mLastNetworkInfo = null;
+ mScanner.pause();
+ }
+ if (mListener != null) {
+ mListener.onWifiStateChanged(state);
+ }
+ }
+
+ public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
+ boolean includeScans) {
+ WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans);
+ tracker.forceUpdate();
+ return tracker.getAccessPoints();
+ }
+
+ @VisibleForTesting
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+ updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN));
+ } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
+ WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
+ WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
+ updateAccessPoints();
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ mConnected.set(info.isConnected());
+ if (mListener != null) {
+ mListener.onConnectedChanged();
+ }
+ updateAccessPoints();
+ updateNetworkInfo(info);
+ } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
+ updateNetworkInfo(null);
+ }
+ }
+ };
+
+ @VisibleForTesting
+ class Scanner extends Handler {
+ private int mRetry = 0;
+
+ void resume() {
+ if (!hasMessages(0)) {
+ sendEmptyMessage(0);
+ }
+ }
+
+ void forceScan() {
+ removeMessages(0);
+ sendEmptyMessage(0);
+ }
+
+ void pause() {
+ mRetry = 0;
+ removeMessages(0);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ if (mWifiManager.startScan()) {
+ mRetry = 0;
+ } else if (++mRetry >= 3) {
+ mRetry = 0;
+ if (mContext != null) {
+ Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
+ }
+ return;
+ }
+ sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
+ }
+ }
+
+ /** A restricted multimap for use in constructAccessPoints */
+ private static class Multimap<K,V> {
+ private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
+ /** retrieve a non-null list of values with key K */
+ List<V> getAll(K key) {
+ List<V> values = store.get(key);
+ return values != null ? values : Collections.<V>emptyList();
+ }
+
+ void put(K key, V val) {
+ List<V> curVals = store.get(key);
+ if (curVals == null) {
+ curVals = new ArrayList<V>(3);
+ store.put(key, curVals);
+ }
+ curVals.add(val);
+ }
+ }
+
+ public interface WifiListener {
+ /**
+ * Called when the state of Wifi has changed, the state will be one of
+ * the following.
+ *
+ * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
+ * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
+ * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
+ * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
+ * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
+ * <p>
+ *
+ * @param state The new state of wifi.
+ */
+ void onWifiStateChanged(int state);
+
+ /**
+ * Called when the connection state of wifi has changed and isConnected
+ * should be called to get the updated state.
+ */
+ void onConnectedChanged();
+
+ /**
+ * Called to indicate the list of AccessPoints has been updated and
+ * getAccessPoints should be called to get the latest information.
+ */
+ void onAccessPointsChanged();
+ }
+}