diff options
author | Jason Monk <jmonk@google.com> | 2015-01-28 10:40:41 -0500 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2015-02-04 15:08:55 -0500 |
commit | d52356aa5e82c7c5db61672bbe8d0f44861f3e59 (patch) | |
tree | b2ccbf3e88c9bacba4921e81b037514156f64c4c /packages/SettingsLib/src/com | |
parent | 1a81b83e1ef3ccf13cf32bb621537a6bda5b33f7 (diff) | |
download | frameworks_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')
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(); + } +} |