blob: ce393f10d77341a58863eae3bcb9e73463bb2635 [file] [log] [blame]
Jason Monkd52356a2015-01-28 10:40:41 -05001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settingslib.wifi;
18
Jason Monk6980d122015-06-15 10:07:55 -040019import android.app.AppGlobals;
Jason Monkd52356a2015-01-28 10:40:41 -050020import android.content.Context;
Jason Monk6980d122015-06-15 10:07:55 -040021import android.content.pm.ApplicationInfo;
22import android.content.pm.IPackageManager;
23import android.content.pm.PackageManager;
Sanket Padawe7094d222015-05-01 16:55:00 -070024import android.net.ConnectivityManager;
25import android.net.Network;
26import android.net.NetworkCapabilities;
Jason Monkd52356a2015-01-28 10:40:41 -050027import android.net.NetworkInfo;
28import android.net.NetworkInfo.DetailedState;
29import android.net.NetworkInfo.State;
Sanket Padawe7094d222015-05-01 16:55:00 -070030import android.net.wifi.IWifiManager;
Jason Monkd52356a2015-01-28 10:40:41 -050031import android.net.wifi.ScanResult;
32import android.net.wifi.WifiConfiguration;
33import android.net.wifi.WifiConfiguration.KeyMgmt;
34import android.net.wifi.WifiInfo;
35import android.net.wifi.WifiManager;
36import android.os.Bundle;
Sanket Padawe7094d222015-05-01 16:55:00 -070037import android.os.RemoteException;
38import android.os.ServiceManager;
Jason Monk6980d122015-06-15 10:07:55 -040039import android.os.UserHandle;
40import android.text.Spannable;
41import android.text.SpannableString;
42import android.text.TextUtils;
43import android.text.style.TtsSpan;
Jason Monkd52356a2015-01-28 10:40:41 -050044import android.util.Log;
45import android.util.LruCache;
46
47import com.android.settingslib.R;
48
Vinit Deshpandefcd46122015-06-11 18:22:23 -070049import java.util.ArrayList;
Jason Monkd52356a2015-01-28 10:40:41 -050050import java.util.Map;
51
52
53public class AccessPoint implements Comparable<AccessPoint> {
54 static final String TAG = "SettingsLib.AccessPoint";
55
56 /**
57 * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
58 */
59 public static final int LOWER_FREQ_24GHZ = 2400;
60
61 /**
62 * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
63 */
64 public static final int HIGHER_FREQ_24GHZ = 2500;
65
66 /**
67 * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
68 */
69 public static final int LOWER_FREQ_5GHZ = 4900;
70
71 /**
72 * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
73 */
74 public static final int HIGHER_FREQ_5GHZ = 5900;
75
76
77 /**
78 * Experimental: we should be able to show the user the list of BSSIDs and bands
79 * for that SSID.
80 * For now this data is used only with Verbose Logging so as to show the band and number
81 * of BSSIDs on which that network is seen.
82 */
Vinit Deshpandefcd46122015-06-11 18:22:23 -070083 public LruCache<String, ScanResult> mScanResultCache = new LruCache<String, ScanResult>(32);
84
Jason Monkd52356a2015-01-28 10:40:41 -050085 private static final String KEY_NETWORKINFO = "key_networkinfo";
86 private static final String KEY_WIFIINFO = "key_wifiinfo";
87 private static final String KEY_SCANRESULT = "key_scanresult";
Vinit Deshpandefcd46122015-06-11 18:22:23 -070088 private static final String KEY_SSID = "key_ssid";
89 private static final String KEY_SECURITY = "key_security";
90 private static final String KEY_PSKTYPE = "key_psktype";
91 private static final String KEY_SCANRESULTCACHE = "key_scanresultcache";
Jason Monkd52356a2015-01-28 10:40:41 -050092 private static final String KEY_CONFIG = "key_config";
93
94 /**
95 * These values are matched in string arrays -- changes must be kept in sync
96 */
97 public static final int SECURITY_NONE = 0;
98 public static final int SECURITY_WEP = 1;
99 public static final int SECURITY_PSK = 2;
100 public static final int SECURITY_EAP = 3;
101
102 private static final int PSK_UNKNOWN = 0;
103 private static final int PSK_WPA = 1;
104 private static final int PSK_WPA2 = 2;
105 private static final int PSK_WPA_WPA2 = 3;
106
107 private static final int VISIBILITY_OUTDATED_AGE_IN_MILLI = 20000;
108 private final Context mContext;
109
110 private String ssid;
111 private int security;
112 private int networkId = WifiConfiguration.INVALID_NETWORK_ID;
113
114 private int pskType = PSK_UNKNOWN;
115
116 private WifiConfiguration mConfig;
Jason Monkd52356a2015-01-28 10:40:41 -0500117
118 private int mRssi = Integer.MAX_VALUE;
119 private long mSeen = 0;
120
121 private WifiInfo mInfo;
122 private NetworkInfo mNetworkInfo;
123 private AccessPointListener mAccessPointListener;
124
125 private Object mTag;
126
127 public AccessPoint(Context context, Bundle savedState) {
128 mContext = context;
129 mConfig = savedState.getParcelable(KEY_CONFIG);
130 if (mConfig != null) {
131 loadConfig(mConfig);
132 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700133 if (savedState.containsKey(KEY_SSID)) {
134 ssid = savedState.getString(KEY_SSID);
135 }
136 if (savedState.containsKey(KEY_SECURITY)) {
137 security = savedState.getInt(KEY_SECURITY);
138 }
139 if (savedState.containsKey(KEY_PSKTYPE)) {
140 pskType = savedState.getInt(KEY_PSKTYPE);
Jason Monkd52356a2015-01-28 10:40:41 -0500141 }
142 mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
143 if (savedState.containsKey(KEY_NETWORKINFO)) {
144 mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
145 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700146 if (savedState.containsKey(KEY_SCANRESULTCACHE)) {
147 ArrayList<ScanResult> scanResultArrayList =
148 savedState.getParcelableArrayList(KEY_SCANRESULTCACHE);
149 mScanResultCache.evictAll();
150 for (ScanResult result : scanResultArrayList) {
151 mScanResultCache.put(result.BSSID, result);
152 }
153 }
Mitchell Wills5a42db22015-08-03 09:46:08 -0700154 update(mConfig, mInfo, mNetworkInfo);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700155 mRssi = getRssi();
156 mSeen = getSeen();
Jason Monkd52356a2015-01-28 10:40:41 -0500157 }
158
159 AccessPoint(Context context, ScanResult result) {
160 mContext = context;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700161 initWithScanResult(result);
Jason Monkd52356a2015-01-28 10:40:41 -0500162 }
163
164 AccessPoint(Context context, WifiConfiguration config) {
165 mContext = context;
166 loadConfig(config);
167 }
168
169 @Override
170 public int compareTo(AccessPoint other) {
171 // Active one goes first.
172 if (isActive() && !other.isActive()) return -1;
173 if (!isActive() && other.isActive()) return 1;
174
175 // Reachable one goes before unreachable one.
176 if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
177 if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
178
179 // Configured one goes before unconfigured one.
180 if (networkId != WifiConfiguration.INVALID_NETWORK_ID
181 && other.networkId == WifiConfiguration.INVALID_NETWORK_ID) return -1;
182 if (networkId == WifiConfiguration.INVALID_NETWORK_ID
183 && other.networkId != WifiConfiguration.INVALID_NETWORK_ID) return 1;
184
185 // Sort by signal strength.
186 int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi);
187 if (difference != 0) {
188 return difference;
189 }
190 // Sort by ssid.
191 return ssid.compareToIgnoreCase(other.ssid);
192 }
193
194 @Override
195 public boolean equals(Object other) {
196 if (!(other instanceof AccessPoint)) return false;
197 return (this.compareTo((AccessPoint) other) == 0);
198 }
199
200 @Override
201 public int hashCode() {
202 int result = 0;
203 if (mInfo != null) result += 13 * mInfo.hashCode();
204 result += 19 * mRssi;
205 result += 23 * networkId;
206 result += 29 * ssid.hashCode();
207 return result;
208 }
209
210 @Override
211 public String toString() {
212 StringBuilder builder = new StringBuilder().append("AccessPoint(")
213 .append(ssid);
214 if (isSaved()) {
215 builder.append(',').append("saved");
216 }
217 if (isActive()) {
218 builder.append(',').append("active");
219 }
220 if (isEphemeral()) {
221 builder.append(',').append("ephemeral");
222 }
223 if (isConnectable()) {
224 builder.append(',').append("connectable");
225 }
226 if (security != SECURITY_NONE) {
227 builder.append(',').append(securityToString(security, pskType));
228 }
229 return builder.append(')').toString();
230 }
231
232 public boolean matches(ScanResult result) {
233 return ssid.equals(result.SSID) && security == getSecurity(result);
234 }
235
236 public boolean matches(WifiConfiguration config) {
Bartosz Fabianowski6fb07562016-01-12 15:43:19 +0100237 if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) {
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700238 return config.FQDN.equals(mConfig.providerFriendlyName);
Bartosz Fabianowski6fb07562016-01-12 15:43:19 +0100239 } else {
240 return ssid.equals(removeDoubleQuotes(config.SSID))
241 && security == getSecurity(config)
242 && (mConfig == null || mConfig.shared == config.shared);
243 }
Jason Monkd52356a2015-01-28 10:40:41 -0500244 }
245
246 public WifiConfiguration getConfig() {
247 return mConfig;
248 }
249
250 public void clearConfig() {
251 mConfig = null;
252 networkId = WifiConfiguration.INVALID_NETWORK_ID;
253 }
254
255 public WifiInfo getInfo() {
256 return mInfo;
257 }
258
259 public int getLevel() {
260 if (mRssi == Integer.MAX_VALUE) {
261 return -1;
262 }
263 return WifiManager.calculateSignalLevel(mRssi, 4);
264 }
265
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700266 public int getRssi() {
267 int rssi = Integer.MIN_VALUE;
268 for (ScanResult result : mScanResultCache.snapshot().values()) {
269 if (result.level > rssi) {
270 rssi = result.level;
271 }
272 }
273
274 return rssi;
275 }
276
277 public long getSeen() {
278 long seen = 0;
279 for (ScanResult result : mScanResultCache.snapshot().values()) {
280 if (result.timestamp > seen) {
281 seen = result.timestamp;
282 }
283 }
284
285 return seen;
286 }
287
Jason Monkd52356a2015-01-28 10:40:41 -0500288 public NetworkInfo getNetworkInfo() {
289 return mNetworkInfo;
290 }
291
292 public int getSecurity() {
293 return security;
294 }
295
296 public String getSecurityString(boolean concise) {
297 Context context = mContext;
Sanket Padawed1878802015-05-12 10:27:19 -0700298 if (mConfig != null && mConfig.isPasspoint()) {
Sanket Padawe194cce62015-07-10 10:53:31 -0700299 return concise ? context.getString(R.string.wifi_security_short_eap) :
300 context.getString(R.string.wifi_security_eap);
Sanket Padawed1878802015-05-12 10:27:19 -0700301 }
Jason Monkd52356a2015-01-28 10:40:41 -0500302 switch(security) {
303 case SECURITY_EAP:
304 return concise ? context.getString(R.string.wifi_security_short_eap) :
305 context.getString(R.string.wifi_security_eap);
306 case SECURITY_PSK:
307 switch (pskType) {
308 case PSK_WPA:
309 return concise ? context.getString(R.string.wifi_security_short_wpa) :
310 context.getString(R.string.wifi_security_wpa);
311 case PSK_WPA2:
312 return concise ? context.getString(R.string.wifi_security_short_wpa2) :
313 context.getString(R.string.wifi_security_wpa2);
314 case PSK_WPA_WPA2:
315 return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
316 context.getString(R.string.wifi_security_wpa_wpa2);
317 case PSK_UNKNOWN:
318 default:
319 return concise ? context.getString(R.string.wifi_security_short_psk_generic)
320 : context.getString(R.string.wifi_security_psk_generic);
321 }
322 case SECURITY_WEP:
323 return concise ? context.getString(R.string.wifi_security_short_wep) :
324 context.getString(R.string.wifi_security_wep);
325 case SECURITY_NONE:
326 default:
327 return concise ? "" : context.getString(R.string.wifi_security_none);
328 }
329 }
330
Jason Monk6980d122015-06-15 10:07:55 -0400331 public String getSsidStr() {
Jason Monkd52356a2015-01-28 10:40:41 -0500332 return ssid;
333 }
334
Jason Monk6980d122015-06-15 10:07:55 -0400335 public CharSequence getSsid() {
336 SpannableString str = new SpannableString(ssid);
337 str.setSpan(new TtsSpan.VerbatimBuilder(ssid).build(), 0, ssid.length(),
338 Spannable.SPAN_INCLUSIVE_INCLUSIVE);
339 return str;
340 }
341
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700342 public String getConfigName() {
343 if (mConfig != null && mConfig.isPasspoint()) {
344 return mConfig.providerFriendlyName;
345 } else {
346 return ssid;
347 }
348 }
349
Jason Monkd52356a2015-01-28 10:40:41 -0500350 public DetailedState getDetailedState() {
351 return mNetworkInfo != null ? mNetworkInfo.getDetailedState() : null;
352 }
353
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700354 public String getSavedNetworkSummary() {
Sanket Padawe56cfbfb2015-05-05 20:10:46 -0700355 if (mConfig != null) {
356 PackageManager pm = mContext.getPackageManager();
357 String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID);
358 int userId = UserHandle.getUserId(mConfig.creatorUid);
359 ApplicationInfo appInfo = null;
360 if (mConfig.creatorName != null && mConfig.creatorName.equals(systemName)) {
361 appInfo = mContext.getApplicationInfo();
362 } else {
363 try {
364 IPackageManager ipm = AppGlobals.getPackageManager();
365 appInfo = ipm.getApplicationInfo(mConfig.creatorName, 0 /* flags */, userId);
366 } catch (RemoteException rex) {
367 }
368 }
369 if (appInfo != null &&
370 !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) &&
371 !appInfo.packageName.equals(
372 mContext.getString(R.string.certinstaller_package))) {
373 return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm));
374 }
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700375 }
Sanket Padawe56cfbfb2015-05-05 20:10:46 -0700376 return "";
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700377 }
378
Jason Monkd52356a2015-01-28 10:40:41 -0500379 public String getSummary() {
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700380 return getSettingsSummary();
381 }
382
383 public String getSettingsSummary() {
Jason Monkd52356a2015-01-28 10:40:41 -0500384 // Update to new summary
385 StringBuilder summary = new StringBuilder();
386
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700387 if (isActive() && mConfig != null && mConfig.isPasspoint()) {
388 // This is the active connection on passpoint
Jason Monkd52356a2015-01-28 10:40:41 -0500389 summary.append(getSummary(mContext, getDetailedState(),
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700390 false, mConfig.providerFriendlyName));
391 } else if (isActive()) {
392 // This is the active connection on non-passpoint network
393 summary.append(getSummary(mContext, getDetailedState(),
Shirish Kalelec7a38ef2015-06-25 13:55:33 -0700394 mInfo != null && mInfo.isEphemeral()));
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700395 } else if (mConfig != null && mConfig.isPasspoint()) {
396 String format = mContext.getString(R.string.available_via_passpoint);
397 summary.append(String.format(format, mConfig.providerFriendlyName));
398 } else if (mConfig != null && mConfig.hasNoInternetAccess()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500399 summary.append(mContext.getString(R.string.wifi_no_internet));
xinhe8d106782015-12-01 14:44:37 -0800400 } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
401 WifiConfiguration.NetworkSelectionStatus networkStatus =
402 mConfig.getNetworkSelectionStatus();
403 switch (networkStatus.getNetworkSelectionDisableReason()) {
404 case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
Jason Monkd52356a2015-01-28 10:40:41 -0500405 summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
xinhe8d106782015-12-01 14:44:37 -0800406 break;
407 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
408 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
409 summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
410 break;
411 case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
412 summary.append(mContext.getString(R.string.wifi_disabled_generic));
413 break;
Jason Monkd52356a2015-01-28 10:40:41 -0500414 }
415 } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
416 summary.append(mContext.getString(R.string.wifi_not_in_range));
417 } else { // In range, not disabled.
418 if (mConfig != null) { // Is saved network
419 summary.append(mContext.getString(R.string.wifi_remembered));
420 }
421 }
422
423 if (WifiTracker.sVerboseLogging > 0) {
424 // Add RSSI/band information for this config, what was seen up to 6 seconds ago
425 // verbose WiFi Logging is only turned on thru developers settings
426 if (mInfo != null && mNetworkInfo != null) { // This is the active connection
427 summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
428 }
429 summary.append(" " + getVisibilityStatus());
xinhe8d106782015-12-01 14:44:37 -0800430 if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
431 summary.append(" (" + mConfig.getNetworkSelectionStatus().getNetworkStatusString());
432 if (mConfig.getNetworkSelectionStatus().getDisableTime() > 0) {
Jason Monkd52356a2015-01-28 10:40:41 -0500433 long now = System.currentTimeMillis();
xinhe8d106782015-12-01 14:44:37 -0800434 long diff = (now - mConfig.getNetworkSelectionStatus().getDisableTime()) / 1000;
Jason Monkd52356a2015-01-28 10:40:41 -0500435 long sec = diff%60; //seconds
436 long min = (diff/60)%60; //minutes
437 long hour = (min/60)%60; //hours
438 summary.append(", ");
439 if (hour > 0) summary.append(Long.toString(hour) + "h ");
440 summary.append( Long.toString(min) + "m ");
441 summary.append( Long.toString(sec) + "s ");
442 }
443 summary.append(")");
444 }
xinhe8d106782015-12-01 14:44:37 -0800445
446 if (mConfig != null) {
447 WifiConfiguration.NetworkSelectionStatus networkStatus =
448 mConfig.getNetworkSelectionStatus();
449 for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
450 index < WifiConfiguration.NetworkSelectionStatus
451 .NETWORK_SELECTION_DISABLED_MAX; index++) {
452 if (networkStatus.getDisableReasonCounter(index) != 0) {
453 summary.append(" " + WifiConfiguration.NetworkSelectionStatus
454 .getNetworkDisableReasonString(index) + "="
455 + networkStatus.getDisableReasonCounter(index));
456 }
457 }
Jason Monkd52356a2015-01-28 10:40:41 -0500458 }
459 }
460 return summary.toString();
461 }
462
463 /**
464 * Returns the visibility status of the WifiConfiguration.
465 *
466 * @return autojoin debugging information
467 * TODO: use a string formatter
468 * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
469 * For instance [-40,5/-30,2]
470 */
471 private String getVisibilityStatus() {
472 StringBuilder visibility = new StringBuilder();
473 StringBuilder scans24GHz = null;
474 StringBuilder scans5GHz = null;
475 String bssid = null;
476
477 long now = System.currentTimeMillis();
478
479 if (mInfo != null) {
480 bssid = mInfo.getBSSID();
481 if (bssid != null) {
482 visibility.append(" ").append(bssid);
483 }
484 visibility.append(" rssi=").append(mInfo.getRssi());
485 visibility.append(" ");
486 visibility.append(" score=").append(mInfo.score);
487 visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
488 visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
489 visibility.append(String.format("%.1f ", mInfo.txBadRate));
490 visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
491 }
492
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700493 int rssi5 = WifiConfiguration.INVALID_RSSI;
494 int rssi24 = WifiConfiguration.INVALID_RSSI;
495 int num5 = 0;
496 int num24 = 0;
497 int numBlackListed = 0;
498 int n24 = 0; // Number scan results we included in the string
499 int n5 = 0; // Number scan results we included in the string
500 Map<String, ScanResult> list = mScanResultCache.snapshot();
501 // TODO: sort list by RSSI or age
502 for (ScanResult result : list.values()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500503
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700504 if (result.frequency >= LOWER_FREQ_5GHZ
505 && result.frequency <= HIGHER_FREQ_5GHZ) {
506 // Strictly speaking: [4915, 5825]
507 // number of known BSSID on 5GHz band
508 num5 = num5 + 1;
509 } else if (result.frequency >= LOWER_FREQ_24GHZ
510 && result.frequency <= HIGHER_FREQ_24GHZ) {
511 // Strictly speaking: [2412, 2482]
512 // number of known BSSID on 2.4Ghz band
513 num24 = num24 + 1;
Jason Monkd52356a2015-01-28 10:40:41 -0500514 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700515
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700516
517 if (result.frequency >= LOWER_FREQ_5GHZ
518 && result.frequency <= HIGHER_FREQ_5GHZ) {
519 if (result.level > rssi5) {
520 rssi5 = result.level;
Jason Monkd52356a2015-01-28 10:40:41 -0500521 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700522 if (n5 < 4) {
523 if (scans5GHz == null) scans5GHz = new StringBuilder();
524 scans5GHz.append(" \n{").append(result.BSSID);
525 if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*");
526 scans5GHz.append("=").append(result.frequency);
527 scans5GHz.append(",").append(result.level);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700528 scans5GHz.append("}");
529 n5++;
Jason Monkd52356a2015-01-28 10:40:41 -0500530 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700531 } else if (result.frequency >= LOWER_FREQ_24GHZ
532 && result.frequency <= HIGHER_FREQ_24GHZ) {
533 if (result.level > rssi24) {
534 rssi24 = result.level;
535 }
536 if (n24 < 4) {
537 if (scans24GHz == null) scans24GHz = new StringBuilder();
538 scans24GHz.append(" \n{").append(result.BSSID);
539 if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*");
540 scans24GHz.append("=").append(result.frequency);
541 scans24GHz.append(",").append(result.level);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700542 scans24GHz.append("}");
543 n24++;
Jason Monkd52356a2015-01-28 10:40:41 -0500544 }
545 }
546 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700547 visibility.append(" [");
548 if (num24 > 0) {
549 visibility.append("(").append(num24).append(")");
550 if (n24 <= 4) {
551 if (scans24GHz != null) {
552 visibility.append(scans24GHz.toString());
553 }
554 } else {
555 visibility.append("max=").append(rssi24);
556 if (scans24GHz != null) {
557 visibility.append(",").append(scans24GHz.toString());
558 }
559 }
560 }
561 visibility.append(";");
562 if (num5 > 0) {
563 visibility.append("(").append(num5).append(")");
564 if (n5 <= 4) {
565 if (scans5GHz != null) {
566 visibility.append(scans5GHz.toString());
567 }
568 } else {
569 visibility.append("max=").append(rssi5);
570 if (scans5GHz != null) {
571 visibility.append(",").append(scans5GHz.toString());
572 }
573 }
574 }
575 if (numBlackListed > 0)
576 visibility.append("!").append(numBlackListed);
577 visibility.append("]");
Jason Monkd52356a2015-01-28 10:40:41 -0500578
579 return visibility.toString();
580 }
581
582 /**
583 * Return whether this is the active connection.
584 * For ephemeral connections (networkId is invalid), this returns false if the network is
585 * disconnected.
586 */
587 public boolean isActive() {
588 return mNetworkInfo != null &&
589 (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
590 mNetworkInfo.getState() != State.DISCONNECTED);
591 }
592
593 public boolean isConnectable() {
594 return getLevel() != -1 && getDetailedState() == null;
595 }
596
597 public boolean isEphemeral() {
Shirish Kalelec7a38ef2015-06-25 13:55:33 -0700598 return mInfo != null && mInfo.isEphemeral() &&
599 mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED;
Jason Monkd52356a2015-01-28 10:40:41 -0500600 }
601
Vinit Deshpande5b7352c2015-07-09 16:53:12 -0700602 public boolean isPasspoint() {
603 return mConfig != null && mConfig.isPasspoint();
604 }
605
Mitchell Wills5a42db22015-08-03 09:46:08 -0700606 /**
607 * Return whether the given {@link WifiInfo} is for this access point.
608 * If the current AP does not have a network Id then the config is used to
609 * match based on SSID and security.
610 */
611 private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
Vinit Deshpande5b7352c2015-07-09 16:53:12 -0700612 if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
Jason Monkd52356a2015-01-28 10:40:41 -0500613 return networkId == info.getNetworkId();
Mitchell Wills5a42db22015-08-03 09:46:08 -0700614 } else if (config != null) {
615 return matches(config);
616 }
617 else {
Jason Monkd52356a2015-01-28 10:40:41 -0500618 // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
619 // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
620 // TODO: Handle hex string SSIDs.
621 return ssid.equals(removeDoubleQuotes(info.getSSID()));
622 }
623 }
624
625 public boolean isSaved() {
626 return networkId != WifiConfiguration.INVALID_NETWORK_ID;
627 }
628
629 public Object getTag() {
630 return mTag;
631 }
632
633 public void setTag(Object tag) {
634 mTag = tag;
635 }
636
637 /**
638 * Generate and save a default wifiConfiguration with common values.
639 * Can only be called for unsecured networks.
640 */
641 public void generateOpenNetworkConfig() {
642 if (security != SECURITY_NONE)
643 throw new IllegalStateException();
644 if (mConfig != null)
645 return;
646 mConfig = new WifiConfiguration();
647 mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
648 mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
649 }
650
651 void loadConfig(WifiConfiguration config) {
Vinit Deshpandefc406002015-04-15 18:10:55 -0700652 if (config.isPasspoint())
653 ssid = config.providerFriendlyName;
654 else
655 ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700656
Jason Monkd52356a2015-01-28 10:40:41 -0500657 security = getSecurity(config);
658 networkId = config.networkId;
659 mConfig = config;
660 }
661
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700662 private void initWithScanResult(ScanResult result) {
Jason Monkd52356a2015-01-28 10:40:41 -0500663 ssid = result.SSID;
664 security = getSecurity(result);
665 if (security == SECURITY_PSK)
666 pskType = getPskType(result);
667 mRssi = result.level;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700668 mSeen = result.timestamp;
Jason Monkd52356a2015-01-28 10:40:41 -0500669 }
670
671 public void saveWifiState(Bundle savedState) {
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700672 if (ssid != null) savedState.putString(KEY_SSID, getSsidStr());
673 savedState.putInt(KEY_SECURITY, security);
674 savedState.putInt(KEY_PSKTYPE, pskType);
675 if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
Jason Monkd52356a2015-01-28 10:40:41 -0500676 savedState.putParcelable(KEY_WIFIINFO, mInfo);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700677 savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
678 new ArrayList<ScanResult>(mScanResultCache.snapshot().values()));
Jason Monkd52356a2015-01-28 10:40:41 -0500679 if (mNetworkInfo != null) {
680 savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
681 }
682 }
683
684 public void setListener(AccessPointListener listener) {
685 mAccessPointListener = listener;
686 }
687
688 boolean update(ScanResult result) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700689 if (matches(result)) {
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700690 /* Update the LRU timestamp, if BSSID exists */
691 mScanResultCache.get(result.BSSID);
692
693 /* Add or update the scan result for the BSSID */
694 mScanResultCache.put(result.BSSID, result);
695
696 int oldLevel = getLevel();
697 int oldRssi = getRssi();
698 mSeen = getSeen();
699 mRssi = (getRssi() + oldRssi)/2;
700 int newLevel = getLevel();
701
702 if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) {
703 mAccessPointListener.onLevelChanged(this);
Jason Monkd52356a2015-01-28 10:40:41 -0500704 }
705 // This flag only comes from scans, is not easily saved in config
706 if (security == SECURITY_PSK) {
707 pskType = getPskType(result);
708 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700709
Jason Monkd52356a2015-01-28 10:40:41 -0500710 if (mAccessPointListener != null) {
711 mAccessPointListener.onAccessPointChanged(this);
712 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700713
Jason Monkd52356a2015-01-28 10:40:41 -0500714 return true;
715 }
716 return false;
717 }
718
Mitchell Wills5a42db22015-08-03 09:46:08 -0700719 boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
Jason Monkd52356a2015-01-28 10:40:41 -0500720 boolean reorder = false;
Mitchell Wills5a42db22015-08-03 09:46:08 -0700721 if (info != null && isInfoForThisAccessPoint(config, info)) {
Jason Monkd52356a2015-01-28 10:40:41 -0500722 reorder = (mInfo == null);
723 mRssi = info.getRssi();
724 mInfo = info;
725 mNetworkInfo = networkInfo;
726 if (mAccessPointListener != null) {
727 mAccessPointListener.onAccessPointChanged(this);
728 }
729 } else if (mInfo != null) {
730 reorder = true;
731 mInfo = null;
732 mNetworkInfo = null;
733 if (mAccessPointListener != null) {
734 mAccessPointListener.onAccessPointChanged(this);
735 }
736 }
737 return reorder;
738 }
739
Vinit Deshpandefc406002015-04-15 18:10:55 -0700740 void update(WifiConfiguration config) {
741 mConfig = config;
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700742 networkId = config.networkId;
743 if (mAccessPointListener != null) {
744 mAccessPointListener.onAccessPointChanged(this);
745 }
Vinit Deshpandefc406002015-04-15 18:10:55 -0700746 }
Shirish Kalelec7a38ef2015-06-25 13:55:33 -0700747
Jason Monkd52356a2015-01-28 10:40:41 -0500748 public static String getSummary(Context context, String ssid, DetailedState state,
Vinit Deshpandefc406002015-04-15 18:10:55 -0700749 boolean isEphemeral, String passpointProvider) {
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700750 if (state == DetailedState.CONNECTED && ssid == null) {
Vinit Deshpandefc406002015-04-15 18:10:55 -0700751 if (TextUtils.isEmpty(passpointProvider) == false) {
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700752 // Special case for connected + passpoint networks.
Vinit Deshpandefc406002015-04-15 18:10:55 -0700753 String format = context.getString(R.string.connected_via_passpoint);
754 return String.format(format, passpointProvider);
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700755 } else if (isEphemeral) {
Vinit Deshpandefc406002015-04-15 18:10:55 -0700756 // Special case for connected + ephemeral networks.
757 return context.getString(R.string.connected_via_wfa);
758 }
Jason Monkd52356a2015-01-28 10:40:41 -0500759 }
760
Sanket Padawe7094d222015-05-01 16:55:00 -0700761 // Case when there is wifi connected without internet connectivity.
762 final ConnectivityManager cm = (ConnectivityManager)
763 context.getSystemService(Context.CONNECTIVITY_SERVICE);
764 if (state == DetailedState.CONNECTED) {
765 IWifiManager wifiManager = IWifiManager.Stub.asInterface(
766 ServiceManager.getService(Context.WIFI_SERVICE));
767 Network nw;
768
769 try {
770 nw = wifiManager.getCurrentNetwork();
771 } catch (RemoteException e) {
772 nw = null;
773 }
774 NetworkCapabilities nc = cm.getNetworkCapabilities(nw);
775 if (nc != null && !nc.hasCapability(nc.NET_CAPABILITY_VALIDATED)) {
776 return context.getString(R.string.wifi_connected_no_internet);
777 }
778 }
779
Jason Monkd52356a2015-01-28 10:40:41 -0500780 String[] formats = context.getResources().getStringArray((ssid == null)
781 ? R.array.wifi_status : R.array.wifi_status_with_ssid);
782 int index = state.ordinal();
783
784 if (index >= formats.length || formats[index].length() == 0) {
Sanket Padawe3e9e5fa2015-05-28 10:41:14 -0700785 return "";
Jason Monkd52356a2015-01-28 10:40:41 -0500786 }
787 return String.format(formats[index], ssid);
788 }
789
790 public static String getSummary(Context context, DetailedState state, boolean isEphemeral) {
Vinit Deshpandefc406002015-04-15 18:10:55 -0700791 return getSummary(context, null, state, isEphemeral, null);
792 }
793
794 public static String getSummary(Context context, DetailedState state, boolean isEphemeral,
795 String passpointProvider) {
796 return getSummary(context, null, state, isEphemeral, passpointProvider);
Jason Monkd52356a2015-01-28 10:40:41 -0500797 }
798
799 public static String convertToQuotedString(String string) {
800 return "\"" + string + "\"";
801 }
802
803 private static int getPskType(ScanResult result) {
804 boolean wpa = result.capabilities.contains("WPA-PSK");
805 boolean wpa2 = result.capabilities.contains("WPA2-PSK");
806 if (wpa2 && wpa) {
807 return PSK_WPA_WPA2;
808 } else if (wpa2) {
809 return PSK_WPA2;
810 } else if (wpa) {
811 return PSK_WPA;
812 } else {
813 Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
814 return PSK_UNKNOWN;
815 }
816 }
817
818 private static int getSecurity(ScanResult result) {
819 if (result.capabilities.contains("WEP")) {
820 return SECURITY_WEP;
821 } else if (result.capabilities.contains("PSK")) {
822 return SECURITY_PSK;
823 } else if (result.capabilities.contains("EAP")) {
824 return SECURITY_EAP;
825 }
826 return SECURITY_NONE;
827 }
828
829 static int getSecurity(WifiConfiguration config) {
830 if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
831 return SECURITY_PSK;
832 }
833 if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
834 config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
835 return SECURITY_EAP;
836 }
837 return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
838 }
839
840 public static String securityToString(int security, int pskType) {
841 if (security == SECURITY_WEP) {
842 return "WEP";
843 } else if (security == SECURITY_PSK) {
844 if (pskType == PSK_WPA) {
845 return "WPA";
846 } else if (pskType == PSK_WPA2) {
847 return "WPA2";
848 } else if (pskType == PSK_WPA_WPA2) {
849 return "WPA_WPA2";
850 }
851 return "PSK";
852 } else if (security == SECURITY_EAP) {
853 return "EAP";
854 }
855 return "NONE";
856 }
857
858 static String removeDoubleQuotes(String string) {
Jason Monk2b51cc32015-05-13 11:07:53 -0400859 if (TextUtils.isEmpty(string)) {
860 return "";
861 }
Jason Monkd52356a2015-01-28 10:40:41 -0500862 int length = string.length();
863 if ((length > 1) && (string.charAt(0) == '"')
864 && (string.charAt(length - 1) == '"')) {
865 return string.substring(1, length - 1);
866 }
867 return string;
868 }
869
870 public interface AccessPointListener {
871 void onAccessPointChanged(AccessPoint accessPoint);
872 void onLevelChanged(AccessPoint accessPoint);
873 }
874}