AccessPoint.java revision e74dbdd364f0ed5ab1b208e9a5643bddfbafb76f
1/*
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
19import android.annotation.IntDef;
20import android.annotation.MainThread;
21import android.annotation.Nullable;
22import android.app.AppGlobals;
23import android.content.Context;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.IPackageManager;
26import android.content.pm.PackageManager;
27import android.net.ConnectivityManager;
28import android.net.NetworkCapabilities;
29import android.net.NetworkInfo;
30import android.net.NetworkInfo.DetailedState;
31import android.net.NetworkInfo.State;
32import android.net.NetworkKey;
33import android.net.NetworkScoreManager;
34import android.net.NetworkScorerAppData;
35import android.net.ScoredNetwork;
36import android.net.wifi.IWifiManager;
37import android.net.wifi.ScanResult;
38import android.net.wifi.WifiConfiguration;
39import android.net.wifi.WifiConfiguration.KeyMgmt;
40import android.net.wifi.WifiEnterpriseConfig;
41import android.net.wifi.WifiInfo;
42import android.net.wifi.WifiManager;
43import android.net.wifi.WifiNetworkScoreCache;
44import android.net.wifi.hotspot2.PasspointConfiguration;
45import android.os.Bundle;
46import android.os.Handler;
47import android.os.Looper;
48import android.os.Parcelable;
49import android.os.RemoteException;
50import android.os.ServiceManager;
51import android.os.SystemClock;
52import android.os.UserHandle;
53import android.support.annotation.NonNull;
54import android.text.Spannable;
55import android.text.SpannableString;
56import android.text.TextUtils;
57import android.text.style.TtsSpan;
58import android.util.ArraySet;
59import android.util.Log;
60
61import com.android.internal.annotations.VisibleForTesting;
62import com.android.settingslib.R;
63import com.android.settingslib.utils.ThreadUtils;
64
65import java.lang.annotation.Retention;
66import java.lang.annotation.RetentionPolicy;
67import java.util.ArrayList;
68import java.util.Collection;
69import java.util.HashMap;
70import java.util.Iterator;
71import java.util.Map;
72import java.util.Set;
73import java.util.concurrent.atomic.AtomicInteger;
74
75/**
76 * Represents a selectable Wifi Network for use in various wifi selection menus backed by
77 * {@link WifiTracker}.
78 *
79 * <p>An AccessPoint, which would be more fittingly named "WifiNetwork", is an aggregation of
80 * {@link ScanResult ScanResults} along with pertinent metadata (e.g. current connection info,
81 * network scores) required to successfully render the network to the user.
82 */
83public class AccessPoint implements Comparable<AccessPoint> {
84    static final String TAG = "SettingsLib.AccessPoint";
85
86    /**
87     * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
88     */
89    public static final int LOWER_FREQ_24GHZ = 2400;
90
91    /**
92     * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
93     */
94    public static final int HIGHER_FREQ_24GHZ = 2500;
95
96    /**
97     * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
98     */
99    public static final int LOWER_FREQ_5GHZ = 4900;
100
101    /**
102     * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
103     */
104    public static final int HIGHER_FREQ_5GHZ = 5900;
105
106    /** The key which identifies this AccessPoint grouping. */
107    private String mKey;
108
109    @IntDef({Speed.NONE, Speed.SLOW, Speed.MODERATE, Speed.FAST, Speed.VERY_FAST})
110    @Retention(RetentionPolicy.SOURCE)
111    public @interface Speed {
112        /**
113         * Constant value representing an unlabeled / unscored network.
114         */
115        int NONE = 0;
116        /**
117         * Constant value representing a slow speed network connection.
118         */
119        int SLOW = 5;
120        /**
121         * Constant value representing a medium speed network connection.
122         */
123        int MODERATE = 10;
124        /**
125         * Constant value representing a fast speed network connection.
126         */
127        int FAST = 20;
128        /**
129         * Constant value representing a very fast speed network connection.
130         */
131        int VERY_FAST = 30;
132    }
133
134    /** The underlying set of scan results comprising this AccessPoint. */
135    private final ArraySet<ScanResult> mScanResults = new ArraySet<>();
136
137    /**
138     * Map of BSSIDs to scored networks for individual bssids.
139     *
140     * <p>This cache should not be evicted with scan results, as the values here are used to
141     * generate a fallback in the absence of scores for the visible APs.
142     */
143    private final Map<String, TimestampedScoredNetwork> mScoredNetworkCache = new HashMap<>();
144
145    static final String KEY_NETWORKINFO = "key_networkinfo";
146    static final String KEY_WIFIINFO = "key_wifiinfo";
147    static final String KEY_SSID = "key_ssid";
148    static final String KEY_SECURITY = "key_security";
149    static final String KEY_SPEED = "key_speed";
150    static final String KEY_PSKTYPE = "key_psktype";
151    static final String KEY_SCANRESULTS = "key_scanresults";
152    static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache";
153    static final String KEY_CONFIG = "key_config";
154    static final String KEY_FQDN = "key_fqdn";
155    static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
156    static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
157    static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
158    static final String KEY_CARRIER_NAME = "key_carrier_name";
159    static final AtomicInteger sLastId = new AtomicInteger(0);
160
161    /**
162     * These values are matched in string arrays -- changes must be kept in sync
163     */
164    public static final int SECURITY_NONE = 0;
165    public static final int SECURITY_WEP = 1;
166    public static final int SECURITY_PSK = 2;
167    public static final int SECURITY_EAP = 3;
168
169    private static final int PSK_UNKNOWN = 0;
170    private static final int PSK_WPA = 1;
171    private static final int PSK_WPA2 = 2;
172    private static final int PSK_WPA_WPA2 = 3;
173
174    /**
175     * The number of distinct wifi levels.
176     *
177     * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
178     */
179    public static final int SIGNAL_LEVELS = 5;
180
181    public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
182
183    private final Context mContext;
184
185    private String ssid;
186    private String bssid;
187    private int security;
188    private int networkId = WifiConfiguration.INVALID_NETWORK_ID;
189
190    private int pskType = PSK_UNKNOWN;
191
192    private WifiConfiguration mConfig;
193
194    private int mRssi = UNREACHABLE_RSSI;
195
196    private WifiInfo mInfo;
197    private NetworkInfo mNetworkInfo;
198    AccessPointListener mAccessPointListener;
199
200    private Object mTag;
201
202    @Speed private int mSpeed = Speed.NONE;
203    private boolean mIsScoredNetworkMetered = false;
204
205    // used to co-relate internal vs returned accesspoint.
206    int mId;
207
208    /**
209     * Information associated with the {@link PasspointConfiguration}.  Only maintaining
210     * the relevant info to preserve spaces.
211     */
212    private String mFqdn;
213    private String mProviderFriendlyName;
214
215    private boolean mIsCarrierAp = false;
216    /**
217     * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
218     */
219    private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
220    private String mCarrierName = null;
221
222    public AccessPoint(Context context, Bundle savedState) {
223        mContext = context;
224
225        if (savedState.containsKey(KEY_CONFIG)) {
226            mConfig = savedState.getParcelable(KEY_CONFIG);
227        }
228        if (mConfig != null) {
229            loadConfig(mConfig);
230        }
231        if (savedState.containsKey(KEY_SSID)) {
232            ssid = savedState.getString(KEY_SSID);
233        }
234        if (savedState.containsKey(KEY_SECURITY)) {
235            security = savedState.getInt(KEY_SECURITY);
236        }
237        if (savedState.containsKey(KEY_SPEED)) {
238            mSpeed = savedState.getInt(KEY_SPEED);
239        }
240        if (savedState.containsKey(KEY_PSKTYPE)) {
241            pskType = savedState.getInt(KEY_PSKTYPE);
242        }
243        mInfo = savedState.getParcelable(KEY_WIFIINFO);
244        if (savedState.containsKey(KEY_NETWORKINFO)) {
245            mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
246        }
247        if (savedState.containsKey(KEY_SCANRESULTS)) {
248            Parcelable[] scanResults = savedState.getParcelableArray(KEY_SCANRESULTS);
249            mScanResults.clear();
250            for (Parcelable result : scanResults) {
251                mScanResults.add((ScanResult) result);
252            }
253        }
254        if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) {
255            ArrayList<TimestampedScoredNetwork> scoredNetworkArrayList =
256                    savedState.getParcelableArrayList(KEY_SCOREDNETWORKCACHE);
257            for (TimestampedScoredNetwork timedScore : scoredNetworkArrayList) {
258                mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore);
259            }
260        }
261        if (savedState.containsKey(KEY_FQDN)) {
262            mFqdn = savedState.getString(KEY_FQDN);
263        }
264        if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
265            mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
266        }
267        if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
268            mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
269        }
270        if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
271            mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
272        }
273        if (savedState.containsKey(KEY_CARRIER_NAME)) {
274            mCarrierName = savedState.getString(KEY_CARRIER_NAME);
275        }
276        update(mConfig, mInfo, mNetworkInfo);
277
278        // Calculate required fields
279        updateKey();
280        updateRssi();
281
282        mId = sLastId.incrementAndGet();
283    }
284
285    public AccessPoint(Context context, WifiConfiguration config) {
286        mContext = context;
287        loadConfig(config);
288        mId = sLastId.incrementAndGet();
289    }
290
291    /**
292     * Initialize an AccessPoint object for a {@link PasspointConfiguration}.  This is mainly
293     * used by "Saved Networks" page for managing the saved {@link PasspointConfiguration}.
294     */
295    public AccessPoint(Context context, PasspointConfiguration config) {
296        mContext = context;
297        mFqdn = config.getHomeSp().getFqdn();
298        mProviderFriendlyName = config.getHomeSp().getFriendlyName();
299        mId = sLastId.incrementAndGet();
300    }
301
302    AccessPoint(Context context, Collection<ScanResult> results) {
303        mContext = context;
304
305        if (results.isEmpty()) {
306            throw new IllegalArgumentException("Cannot construct with an empty ScanResult list");
307        }
308        mScanResults.addAll(results);
309
310        // Information derived from scan results
311        ScanResult firstResult = results.iterator().next();
312        ssid = firstResult.SSID;
313        bssid = firstResult.BSSID;
314        security = getSecurity(firstResult);
315        if (security == SECURITY_PSK) {
316            pskType = getPskType(firstResult);
317        }
318        updateKey();
319        updateRssi();
320
321        // Passpoint Info
322        mIsCarrierAp = firstResult.isCarrierAp;
323        mCarrierApEapType = firstResult.carrierApEapType;
324        mCarrierName = firstResult.carrierName;
325
326        mId = sLastId.incrementAndGet();
327    }
328
329    @VisibleForTesting void loadConfig(WifiConfiguration config) {
330        ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
331        bssid = config.BSSID;
332        security = getSecurity(config);
333        updateKey();
334        networkId = config.networkId;
335        mConfig = config;
336    }
337
338    /** Updates {@link #mKey} and should only called upon object creation/initialization. */
339    private void updateKey() {
340        // TODO(sghuman): Consolidate Key logic on ScanResultMatchInfo
341
342        StringBuilder builder = new StringBuilder();
343
344        if (TextUtils.isEmpty(getSsidStr())) {
345            builder.append(getBssid());
346        } else {
347            builder.append(getSsidStr());
348        }
349
350        builder.append(',').append(getSecurity());
351        mKey = builder.toString();
352    }
353
354    /**
355    * Returns a negative integer, zero, or a positive integer if this AccessPoint is less than,
356    * equal to, or greater than the other AccessPoint.
357    *
358    * Sort order rules for AccessPoints:
359    *   1. Active before inactive
360    *   2. Reachable before unreachable
361    *   3. Saved before unsaved
362    *   4. Network speed value
363    *   5. Stronger signal before weaker signal
364    *   6. SSID alphabetically
365    *
366    * Note that AccessPoints with a signal are usually also Reachable,
367    * and will thus appear before unreachable saved AccessPoints.
368    */
369    @Override
370    public int compareTo(@NonNull AccessPoint other) {
371        // Active one goes first.
372        if (isActive() && !other.isActive()) return -1;
373        if (!isActive() && other.isActive()) return 1;
374
375        // Reachable one goes before unreachable one.
376        if (isReachable() && !other.isReachable()) return -1;
377        if (!isReachable() && other.isReachable()) return 1;
378
379        // Configured (saved) one goes before unconfigured one.
380        if (isSaved() && !other.isSaved()) return -1;
381        if (!isSaved() && other.isSaved()) return 1;
382
383        // Faster speeds go before slower speeds - but only if visible change in speed label
384        if (getSpeed() != other.getSpeed()) {
385            return other.getSpeed() - getSpeed();
386        }
387
388        // Sort by signal strength, bucketed by level
389        int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS)
390                - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
391        if (difference != 0) {
392            return difference;
393        }
394
395        // Sort by ssid.
396        difference = getSsidStr().compareToIgnoreCase(other.getSsidStr());
397        if (difference != 0) {
398            return difference;
399        }
400
401        // Do a case sensitive comparison to distinguish SSIDs that differ in case only
402        return getSsidStr().compareTo(other.getSsidStr());
403    }
404
405    @Override
406    public boolean equals(Object other) {
407        if (!(other instanceof AccessPoint)) return false;
408        return (this.compareTo((AccessPoint) other) == 0);
409    }
410
411    @Override
412    public int hashCode() {
413        int result = 0;
414        if (mInfo != null) result += 13 * mInfo.hashCode();
415        result += 19 * mRssi;
416        result += 23 * networkId;
417        result += 29 * ssid.hashCode();
418        return result;
419    }
420
421    @Override
422    public String toString() {
423        StringBuilder builder = new StringBuilder().append("AccessPoint(")
424                .append(ssid);
425        if (bssid != null) {
426            builder.append(":").append(bssid);
427        }
428        if (isSaved()) {
429            builder.append(',').append("saved");
430        }
431        if (isActive()) {
432            builder.append(',').append("active");
433        }
434        if (isEphemeral()) {
435            builder.append(',').append("ephemeral");
436        }
437        if (isConnectable()) {
438            builder.append(',').append("connectable");
439        }
440        if (security != SECURITY_NONE) {
441            builder.append(',').append(securityToString(security, pskType));
442        }
443        builder.append(",level=").append(getLevel());
444        if (mSpeed != Speed.NONE) {
445            builder.append(",speed=").append(mSpeed);
446        }
447        builder.append(",metered=").append(isMetered());
448
449        if (isVerboseLoggingEnabled()) {
450            builder.append(",rssi=").append(mRssi);
451            builder.append(",scan cache size=").append(mScanResults.size());
452        }
453
454        return builder.append(')').toString();
455    }
456
457    /**
458     * Updates the AccessPoint rankingScore, metering, and speed, returning true if the data has
459     * changed.
460     *
461     * @param scoreCache The score cache to use to retrieve scores
462     * @param scoringUiEnabled Whether to show scoring and badging UI
463     * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when
464     *         generating speed labels
465     */
466    boolean update(
467            WifiNetworkScoreCache scoreCache,
468            boolean scoringUiEnabled,
469            long maxScoreCacheAgeMillis) {
470        boolean scoreChanged = false;
471        if (scoringUiEnabled) {
472            scoreChanged = updateScores(scoreCache, maxScoreCacheAgeMillis);
473        }
474        return updateMetered(scoreCache) || scoreChanged;
475    }
476
477    /**
478     * Updates the AccessPoint rankingScore and speed, returning true if the data has changed.
479     *
480     * <p>Any cached {@link TimestampedScoredNetwork} objects older than the given max age in millis
481     * will be removed when this method is invoked.
482     *
483     * <p>Precondition: {@link #mRssi} is up to date before invoking this method.
484     *
485     * @param scoreCache The score cache to use to retrieve scores
486     * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when
487     *         generating speed labels
488     *
489     * @return true if the set speed has changed
490     */
491    private boolean updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis) {
492        long nowMillis = SystemClock.elapsedRealtime();
493        for (ScanResult result : mScanResults) {
494            ScoredNetwork score = scoreCache.getScoredNetwork(result);
495            if (score == null) {
496                continue;
497            }
498            TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
499            if (timedScore == null) {
500                mScoredNetworkCache.put(
501                        result.BSSID, new TimestampedScoredNetwork(score, nowMillis));
502            } else {
503                // Update data since the has been seen in the score cache
504                timedScore.update(score, nowMillis);
505            }
506        }
507
508        // Remove old cached networks
509        long evictionCutoff = nowMillis - maxScoreCacheAgeMillis;
510        Iterator<TimestampedScoredNetwork> iterator = mScoredNetworkCache.values().iterator();
511        iterator.forEachRemaining(timestampedScoredNetwork -> {
512            if (timestampedScoredNetwork.getUpdatedTimestampMillis() < evictionCutoff) {
513                iterator.remove();
514            }
515        });
516
517        return updateSpeed();
518    }
519
520    /**
521     * Updates the internal speed, returning true if the update resulted in a speed label change.
522     */
523    private boolean updateSpeed() {
524        int oldSpeed = mSpeed;
525        mSpeed = generateAverageSpeedForSsid();
526
527        boolean changed = oldSpeed != mSpeed;
528        if(isVerboseLoggingEnabled() && changed) {
529            Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed));
530        }
531        return changed;
532    }
533
534    /** Creates a speed value for the current {@link #mRssi} by averaging all non zero badges. */
535    @Speed private int generateAverageSpeedForSsid() {
536        if (mScoredNetworkCache.isEmpty()) {
537            return Speed.NONE;
538        }
539
540        if (Log.isLoggable(TAG, Log.DEBUG)) {
541            Log.d(TAG, String.format("Generating fallbackspeed for %s using cache: %s",
542                    getSsidStr(), mScoredNetworkCache));
543        }
544
545        // TODO(b/63073866): If flickering issues persist, consider mapping using getLevel rather
546        // than specific rssi value so score doesn't change without a visible wifi bar change. This
547        // issue is likely to be more evident for the active AP whose RSSI value is not half-lifed.
548
549        int count = 0;
550        int totalSpeed = 0;
551        for (TimestampedScoredNetwork timedScore : mScoredNetworkCache.values()) {
552            int speed = timedScore.getScore().calculateBadge(mRssi);
553            if (speed != Speed.NONE) {
554                count++;
555                totalSpeed += speed;
556            }
557        }
558        int speed = count == 0 ? Speed.NONE : totalSpeed / count;
559        if (isVerboseLoggingEnabled()) {
560            Log.i(TAG, String.format("%s generated fallback speed is: %d", getSsidStr(), speed));
561        }
562        return roundToClosestSpeedEnum(speed);
563    }
564
565    /**
566     * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning
567     * true if the metering changed.
568     */
569    private boolean updateMetered(WifiNetworkScoreCache scoreCache) {
570        boolean oldMetering = mIsScoredNetworkMetered;
571        mIsScoredNetworkMetered = false;
572
573        if (isActive() && mInfo != null) {
574            NetworkKey key = NetworkKey.createFromWifiInfo(mInfo);
575            ScoredNetwork score = scoreCache.getScoredNetwork(key);
576            if (score != null) {
577                mIsScoredNetworkMetered |= score.meteredHint;
578            }
579        } else {
580            for (ScanResult result : mScanResults) {
581                ScoredNetwork score = scoreCache.getScoredNetwork(result);
582                if (score == null) {
583                    continue;
584                }
585                mIsScoredNetworkMetered |= score.meteredHint;
586            }
587        }
588        return oldMetering == mIsScoredNetworkMetered;
589    }
590
591    public static String getKey(ScanResult result) {
592        StringBuilder builder = new StringBuilder();
593
594        if (TextUtils.isEmpty(result.SSID)) {
595            builder.append(result.BSSID);
596        } else {
597            builder.append(result.SSID);
598        }
599
600        builder.append(',').append(getSecurity(result));
601        return builder.toString();
602    }
603
604    public static String getKey(WifiConfiguration config) {
605        StringBuilder builder = new StringBuilder();
606
607        if (TextUtils.isEmpty(config.SSID)) {
608            builder.append(config.BSSID);
609        } else {
610            builder.append(removeDoubleQuotes(config.SSID));
611        }
612
613        builder.append(',').append(getSecurity(config));
614        return builder.toString();
615    }
616
617    public String getKey() {
618        return mKey;
619    }
620
621    public boolean matches(WifiConfiguration config) {
622        if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) {
623            return ssid.equals(removeDoubleQuotes(config.SSID)) && config.FQDN.equals(mConfig.FQDN);
624        } else {
625            return ssid.equals(removeDoubleQuotes(config.SSID))
626                    && security == getSecurity(config)
627                    && (mConfig == null || mConfig.shared == config.shared);
628        }
629    }
630
631    public WifiConfiguration getConfig() {
632        return mConfig;
633    }
634
635    public String getPasspointFqdn() {
636        return mFqdn;
637    }
638
639    public void clearConfig() {
640        mConfig = null;
641        networkId = WifiConfiguration.INVALID_NETWORK_ID;
642    }
643
644    public WifiInfo getInfo() {
645        return mInfo;
646    }
647
648    /**
649     * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
650     *
651     * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
652     * always return at least 0.
653     */
654    public int getLevel() {
655        return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
656    }
657
658    public int getRssi() {
659        return mRssi;
660    }
661
662    /**
663     * Returns the underlying scan result set.
664     *
665     * <p>Callers should not modify this set.
666     */
667    public Set<ScanResult> getScanResults() { return mScanResults; }
668
669    public Map<String, TimestampedScoredNetwork> getScoredNetworkCache() {
670        return mScoredNetworkCache;
671    }
672
673    /**
674     * Updates {@link #mRssi}.
675     *
676     * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned.
677     * If the given AccessPoint is not active, a value will be calculated from previous scan
678     * results, returning the best RSSI for all matching AccessPoints averaged with the previous
679     * value. If the access point is not connected and there are no scan results, the rssi will be
680     * set to {@link #UNREACHABLE_RSSI}.
681     */
682    private void updateRssi() {
683        if (this.isActive()) {
684            return;
685        }
686
687        int rssi = UNREACHABLE_RSSI;
688        for (ScanResult result : mScanResults) {
689            if (result.level > rssi) {
690                rssi = result.level;
691            }
692        }
693
694        if (rssi != UNREACHABLE_RSSI && mRssi != UNREACHABLE_RSSI) {
695            mRssi = (mRssi + rssi) / 2; // half-life previous value
696        } else {
697            mRssi = rssi;
698        }
699    }
700
701    /**
702     * Returns if the network should be considered metered.
703     */
704    public boolean isMetered() {
705        return mIsScoredNetworkMetered
706                || WifiConfiguration.isMetered(mConfig, mInfo);
707    }
708
709    public NetworkInfo getNetworkInfo() {
710        return mNetworkInfo;
711    }
712
713    public int getSecurity() {
714        return security;
715    }
716
717    public String getSecurityString(boolean concise) {
718        Context context = mContext;
719        if (isPasspoint() || isPasspointConfig()) {
720            return concise ? context.getString(R.string.wifi_security_short_eap) :
721                context.getString(R.string.wifi_security_eap);
722        }
723        switch(security) {
724            case SECURITY_EAP:
725                return concise ? context.getString(R.string.wifi_security_short_eap) :
726                    context.getString(R.string.wifi_security_eap);
727            case SECURITY_PSK:
728                switch (pskType) {
729                    case PSK_WPA:
730                        return concise ? context.getString(R.string.wifi_security_short_wpa) :
731                            context.getString(R.string.wifi_security_wpa);
732                    case PSK_WPA2:
733                        return concise ? context.getString(R.string.wifi_security_short_wpa2) :
734                            context.getString(R.string.wifi_security_wpa2);
735                    case PSK_WPA_WPA2:
736                        return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
737                            context.getString(R.string.wifi_security_wpa_wpa2);
738                    case PSK_UNKNOWN:
739                    default:
740                        return concise ? context.getString(R.string.wifi_security_short_psk_generic)
741                                : context.getString(R.string.wifi_security_psk_generic);
742                }
743            case SECURITY_WEP:
744                return concise ? context.getString(R.string.wifi_security_short_wep) :
745                    context.getString(R.string.wifi_security_wep);
746            case SECURITY_NONE:
747            default:
748                return concise ? "" : context.getString(R.string.wifi_security_none);
749        }
750    }
751
752    public String getSsidStr() {
753        return ssid;
754    }
755
756    public String getBssid() {
757        return bssid;
758    }
759
760    public CharSequence getSsid() {
761        final SpannableString str = new SpannableString(ssid);
762        str.setSpan(new TtsSpan.TelephoneBuilder(ssid).build(), 0, ssid.length(),
763                Spannable.SPAN_INCLUSIVE_INCLUSIVE);
764        return str;
765    }
766
767    public String getConfigName() {
768        if (mConfig != null && mConfig.isPasspoint()) {
769            return mConfig.providerFriendlyName;
770        } else if (mFqdn != null) {
771            return mProviderFriendlyName;
772        } else {
773            return ssid;
774        }
775    }
776
777    public DetailedState getDetailedState() {
778        if (mNetworkInfo != null) {
779            return mNetworkInfo.getDetailedState();
780        }
781        Log.w(TAG, "NetworkInfo is null, cannot return detailed state");
782        return null;
783    }
784
785    public boolean isCarrierAp() {
786        return mIsCarrierAp;
787    }
788
789    public int getCarrierApEapType() {
790        return mCarrierApEapType;
791    }
792
793    public String getCarrierName() {
794        return mCarrierName;
795    }
796
797    public String getSavedNetworkSummary() {
798        WifiConfiguration config = mConfig;
799        if (config != null) {
800            PackageManager pm = mContext.getPackageManager();
801            String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID);
802            int userId = UserHandle.getUserId(config.creatorUid);
803            ApplicationInfo appInfo = null;
804            if (config.creatorName != null && config.creatorName.equals(systemName)) {
805                appInfo = mContext.getApplicationInfo();
806            } else {
807                try {
808                    IPackageManager ipm = AppGlobals.getPackageManager();
809                    appInfo = ipm.getApplicationInfo(config.creatorName, 0 /* flags */, userId);
810                } catch (RemoteException rex) {
811                }
812            }
813            if (appInfo != null &&
814                    !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) &&
815                    !appInfo.packageName.equals(
816                    mContext.getString(R.string.certinstaller_package))) {
817                return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm));
818            }
819        }
820        return "";
821    }
822
823    public String getSummary() {
824        return getSettingsSummary(mConfig);
825    }
826
827    public String getSettingsSummary() {
828        return getSettingsSummary(mConfig);
829    }
830
831    private String getSettingsSummary(WifiConfiguration config) {
832        // Update to new summary
833        StringBuilder summary = new StringBuilder();
834
835        if (isActive() && config != null && config.isPasspoint()) {
836            // This is the active connection on passpoint
837            summary.append(getSummary(mContext, getDetailedState(),
838                    false, config.providerFriendlyName));
839        } else if (isActive() && config != null && getDetailedState() == DetailedState.CONNECTED
840                && mIsCarrierAp) {
841            summary.append(String.format(mContext.getString(R.string.connected_via_carrier), mCarrierName));
842        } else if (isActive()) {
843            // This is the active connection on non-passpoint network
844            summary.append(getSummary(mContext, getDetailedState(),
845                    mInfo != null && mInfo.isEphemeral()));
846        } else if (config != null && config.isPasspoint()
847                && config.getNetworkSelectionStatus().isNetworkEnabled()) {
848            String format = mContext.getString(R.string.available_via_passpoint);
849            summary.append(String.format(format, config.providerFriendlyName));
850        } else if (config != null && config.hasNoInternetAccess()) {
851            int messageID = config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
852                    ? R.string.wifi_no_internet_no_reconnect
853                    : R.string.wifi_no_internet;
854            summary.append(mContext.getString(messageID));
855        } else if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
856            WifiConfiguration.NetworkSelectionStatus networkStatus =
857                    config.getNetworkSelectionStatus();
858            switch (networkStatus.getNetworkSelectionDisableReason()) {
859                case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
860                    summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
861                    break;
862                case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
863                    summary.append(mContext.getString(R.string.wifi_check_password_try_again));
864                    break;
865                case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
866                case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
867                    summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
868                    break;
869                case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
870                    summary.append(mContext.getString(R.string.wifi_disabled_generic));
871                    break;
872            }
873        } else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
874            summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
875        } else if (mIsCarrierAp) {
876            summary.append(String.format(mContext.getString(R.string.available_via_carrier), mCarrierName));
877        } else if (!isReachable()) { // Wifi out of range
878            summary.append(mContext.getString(R.string.wifi_not_in_range));
879        } else { // In range, not disabled.
880            if (config != null) { // Is saved network
881                // Last attempt to connect to this failed. Show reason why
882                switch (config.recentFailure.getAssociationStatus()) {
883                    case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA:
884                        summary.append(mContext.getString(
885                                R.string.wifi_ap_unable_to_handle_new_sta));
886                        break;
887                    default:
888                        // "Saved"
889                        summary.append(mContext.getString(R.string.wifi_remembered));
890                        break;
891                }
892            }
893        }
894
895        if (isVerboseLoggingEnabled()) {
896            summary.append(WifiUtils.buildLoggingSummary(this, config));
897        }
898
899        // If Speed label and summary are both present, use the preference combination to combine
900        // the two, else return the non-null one.
901        if (getSpeedLabel() != null && summary.length() != 0) {
902            return mContext.getResources().getString(
903                    R.string.preference_summary_default_combination,
904                    getSpeedLabel(),
905                    summary.toString());
906        } else if (getSpeedLabel() != null) {
907            return getSpeedLabel();
908        } else {
909            return summary.toString();
910        }
911    }
912
913    /**
914     * Return whether this is the active connection.
915     * For ephemeral connections (networkId is invalid), this returns false if the network is
916     * disconnected.
917     */
918    public boolean isActive() {
919        return mNetworkInfo != null &&
920                (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
921                 mNetworkInfo.getState() != State.DISCONNECTED);
922    }
923
924    public boolean isConnectable() {
925        return getLevel() != -1 && getDetailedState() == null;
926    }
927
928    public boolean isEphemeral() {
929        return mInfo != null && mInfo.isEphemeral() &&
930                mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED;
931    }
932
933    /**
934     * Return true if this AccessPoint represents a Passpoint AP.
935     */
936    public boolean isPasspoint() {
937        return mConfig != null && mConfig.isPasspoint();
938    }
939
940    /**
941     * Return true if this AccessPoint represents a Passpoint provider configuration.
942     */
943    public boolean isPasspointConfig() {
944        return mFqdn != null;
945    }
946
947    /**
948     * Return whether the given {@link WifiInfo} is for this access point.
949     * If the current AP does not have a network Id then the config is used to
950     * match based on SSID and security.
951     */
952    private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
953        if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
954            return networkId == info.getNetworkId();
955        } else if (config != null) {
956            return matches(config);
957        }
958        else {
959            // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
960            // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
961            // TODO: Handle hex string SSIDs.
962            return ssid.equals(removeDoubleQuotes(info.getSSID()));
963        }
964    }
965
966    public boolean isSaved() {
967        return networkId != WifiConfiguration.INVALID_NETWORK_ID;
968    }
969
970    public Object getTag() {
971        return mTag;
972    }
973
974    public void setTag(Object tag) {
975        mTag = tag;
976    }
977
978    /**
979     * Generate and save a default wifiConfiguration with common values.
980     * Can only be called for unsecured networks.
981     */
982    public void generateOpenNetworkConfig() {
983        if (security != SECURITY_NONE)
984            throw new IllegalStateException();
985        if (mConfig != null)
986            return;
987        mConfig = new WifiConfiguration();
988        mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
989        mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
990    }
991
992    public void saveWifiState(Bundle savedState) {
993        if (ssid != null) savedState.putString(KEY_SSID, getSsidStr());
994        savedState.putInt(KEY_SECURITY, security);
995        savedState.putInt(KEY_SPEED, mSpeed);
996        savedState.putInt(KEY_PSKTYPE, pskType);
997        if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
998        savedState.putParcelable(KEY_WIFIINFO, mInfo);
999        savedState.putParcelableArray(KEY_SCANRESULTS,
1000                mScanResults.toArray(new Parcelable[mScanResults.size()]));
1001        savedState.putParcelableArrayList(KEY_SCOREDNETWORKCACHE,
1002                new ArrayList<>(mScoredNetworkCache.values()));
1003        if (mNetworkInfo != null) {
1004            savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
1005        }
1006        if (mFqdn != null) {
1007            savedState.putString(KEY_FQDN, mFqdn);
1008        }
1009        if (mProviderFriendlyName != null) {
1010            savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
1011        }
1012        savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
1013        savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
1014        savedState.putString(KEY_CARRIER_NAME, mCarrierName);
1015    }
1016
1017    public void setListener(AccessPointListener listener) {
1018        mAccessPointListener = listener;
1019    }
1020
1021    /**
1022     * Sets {@link #mScanResults} to the given collection.
1023     *
1024     * @param scanResults a collection of scan results to add to the internal set
1025     * @throws IllegalArgumentException if any of the given ScanResults did not belong to this AP
1026     */
1027    void setScanResults(Collection<ScanResult> scanResults) {
1028
1029        // Validate scan results are for current AP only
1030        String key = getKey();
1031        for (ScanResult result : scanResults) {
1032            String scanResultKey = AccessPoint.getKey(result);
1033            if (!mKey.equals(scanResultKey)) {
1034                throw new IllegalArgumentException(
1035                        String.format("ScanResult %s\nkey of %s did not match current AP key %s",
1036                                      result, scanResultKey, key));
1037            }
1038        }
1039
1040
1041        int oldLevel = getLevel();
1042        mScanResults.clear();
1043        mScanResults.addAll(scanResults);
1044        updateRssi();
1045        int newLevel = getLevel();
1046
1047        // If newLevel is 0, there will be no displayed Preference since the AP is unreachable
1048        if (newLevel > 0 && newLevel != oldLevel) {
1049            // Only update labels on visible rssi changes
1050            updateSpeed();
1051            ThreadUtils.postOnMainThread(() -> {
1052                if (mAccessPointListener != null) {
1053                    mAccessPointListener.onLevelChanged(this);
1054                }
1055            });
1056
1057        }
1058
1059        ThreadUtils.postOnMainThread(() -> {
1060            if (mAccessPointListener != null) {
1061                mAccessPointListener.onAccessPointChanged(this);
1062            }
1063        });
1064
1065        if (!scanResults.isEmpty()) {
1066            ScanResult result = scanResults.iterator().next();
1067
1068            // This flag only comes from scans, is not easily saved in config
1069            if (security == SECURITY_PSK) {
1070                pskType = getPskType(result);
1071            }
1072
1073            // The carrier info in the ScanResult is set by the platform based on the SSID and will
1074            // always be the same for all matching scan results.
1075            mIsCarrierAp = result.isCarrierAp;
1076            mCarrierApEapType = result.carrierApEapType;
1077            mCarrierName = result.carrierName;
1078        }
1079    }
1080
1081    /** Attempt to update the AccessPoint and return true if an update occurred. */
1082    public boolean update(
1083            @Nullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
1084
1085        boolean updated = false;
1086        final int oldLevel = getLevel();
1087        if (info != null && isInfoForThisAccessPoint(config, info)) {
1088            updated = (mInfo == null);
1089            if (mConfig != config) {
1090                // We do not set updated = true as we do not want to increase the amount of sorting
1091                // and copying performed in WifiTracker at this time. If issues involving refresh
1092                // are still seen, we will investigate further.
1093                update(config); // Notifies the AccessPointListener of the change
1094            }
1095            if (mRssi != info.getRssi() && info.getRssi() != WifiInfo.INVALID_RSSI) {
1096                mRssi = info.getRssi();
1097                updated = true;
1098            } else if (mNetworkInfo != null && networkInfo != null
1099                    && mNetworkInfo.getDetailedState() != networkInfo.getDetailedState()) {
1100                updated = true;
1101            }
1102            mInfo = info;
1103            mNetworkInfo = networkInfo;
1104        } else if (mInfo != null) {
1105            updated = true;
1106            mInfo = null;
1107            mNetworkInfo = null;
1108        }
1109        if (updated && mAccessPointListener != null) {
1110            ThreadUtils.postOnMainThread(() -> {
1111                if (mAccessPointListener != null) {
1112                    mAccessPointListener.onAccessPointChanged(this);
1113                }
1114            });
1115
1116            if (oldLevel != getLevel() /* current level */) {
1117                ThreadUtils.postOnMainThread(() -> {
1118                    if (mAccessPointListener != null) {
1119                        mAccessPointListener.onLevelChanged(this);
1120                    }
1121                });
1122            }
1123        }
1124
1125        return updated;
1126    }
1127
1128    void update(@Nullable WifiConfiguration config) {
1129        mConfig = config;
1130        networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID;
1131        ThreadUtils.postOnMainThread(() -> {
1132            if (mAccessPointListener != null) {
1133                mAccessPointListener.onAccessPointChanged(this);
1134            }
1135        });
1136    }
1137
1138    @VisibleForTesting
1139    void setRssi(int rssi) {
1140        mRssi = rssi;
1141    }
1142
1143    /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */
1144    void setUnreachable() {
1145        setRssi(AccessPoint.UNREACHABLE_RSSI);
1146    }
1147
1148    int getSpeed() { return mSpeed;}
1149
1150    @Nullable
1151    String getSpeedLabel() {
1152        return getSpeedLabel(mSpeed);
1153    }
1154
1155    @Nullable
1156    @Speed
1157    private static int roundToClosestSpeedEnum(int speed) {
1158        if (speed < Speed.SLOW) {
1159            return Speed.NONE;
1160        } else if (speed < (Speed.SLOW + Speed.MODERATE) / 2) {
1161            return Speed.SLOW;
1162        } else if (speed < (Speed.MODERATE + Speed.FAST) / 2) {
1163            return Speed.MODERATE;
1164        } else if (speed < (Speed.FAST + Speed.VERY_FAST) / 2) {
1165            return Speed.FAST;
1166        } else {
1167            return Speed.VERY_FAST;
1168        }
1169    }
1170
1171    @Nullable
1172    String getSpeedLabel(@Speed int speed) {
1173        return getSpeedLabel(mContext, speed);
1174    }
1175
1176    private static String getSpeedLabel(Context context, int speed) {
1177        switch (speed) {
1178            case Speed.VERY_FAST:
1179                return context.getString(R.string.speed_label_very_fast);
1180            case Speed.FAST:
1181                return context.getString(R.string.speed_label_fast);
1182            case Speed.MODERATE:
1183                return context.getString(R.string.speed_label_okay);
1184            case Speed.SLOW:
1185                return context.getString(R.string.speed_label_slow);
1186            case Speed.NONE:
1187            default:
1188                return null;
1189        }
1190    }
1191
1192    /** Return the speed label for a {@link ScoredNetwork} at the specified {@code rssi} level. */
1193    @Nullable
1194    public static String getSpeedLabel(Context context, ScoredNetwork scoredNetwork, int rssi) {
1195        return getSpeedLabel(context, roundToClosestSpeedEnum(scoredNetwork.calculateBadge(rssi)));
1196    }
1197
1198    /** Return true if the current RSSI is reachable, and false otherwise. */
1199    public boolean isReachable() {
1200        return mRssi != UNREACHABLE_RSSI;
1201    }
1202
1203    public static String getSummary(Context context, String ssid, DetailedState state,
1204            boolean isEphemeral, String passpointProvider) {
1205        if (state == DetailedState.CONNECTED && ssid == null) {
1206            if (TextUtils.isEmpty(passpointProvider) == false) {
1207                // Special case for connected + passpoint networks.
1208                String format = context.getString(R.string.connected_via_passpoint);
1209                return String.format(format, passpointProvider);
1210            } else if (isEphemeral) {
1211                // Special case for connected + ephemeral networks.
1212                final NetworkScoreManager networkScoreManager = context.getSystemService(
1213                        NetworkScoreManager.class);
1214                NetworkScorerAppData scorer = networkScoreManager.getActiveScorer();
1215                if (scorer != null && scorer.getRecommendationServiceLabel() != null) {
1216                    String format = context.getString(R.string.connected_via_network_scorer);
1217                    return String.format(format, scorer.getRecommendationServiceLabel());
1218                } else {
1219                    return context.getString(R.string.connected_via_network_scorer_default);
1220                }
1221            }
1222        }
1223
1224        // Case when there is wifi connected without internet connectivity.
1225        final ConnectivityManager cm = (ConnectivityManager)
1226                context.getSystemService(Context.CONNECTIVITY_SERVICE);
1227        if (state == DetailedState.CONNECTED) {
1228            IWifiManager wifiManager = IWifiManager.Stub.asInterface(
1229                    ServiceManager.getService(Context.WIFI_SERVICE));
1230            NetworkCapabilities nc = null;
1231
1232            try {
1233                nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
1234            } catch (RemoteException e) {}
1235
1236            if (nc != null) {
1237                if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) {
1238                    int id = context.getResources()
1239                            .getIdentifier("network_available_sign_in", "string", "android");
1240                    return context.getString(id);
1241                } else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
1242                    return context.getString(R.string.wifi_connected_no_internet);
1243                }
1244            }
1245        }
1246        if (state == null) {
1247            Log.w(TAG, "state is null, returning empty summary");
1248            return "";
1249        }
1250        String[] formats = context.getResources().getStringArray((ssid == null)
1251                ? R.array.wifi_status : R.array.wifi_status_with_ssid);
1252        int index = state.ordinal();
1253
1254        if (index >= formats.length || formats[index].length() == 0) {
1255            return "";
1256        }
1257        return String.format(formats[index], ssid);
1258    }
1259
1260    public static String getSummary(Context context, DetailedState state, boolean isEphemeral) {
1261        return getSummary(context, null, state, isEphemeral, null);
1262    }
1263
1264    public static String getSummary(Context context, DetailedState state, boolean isEphemeral,
1265            String passpointProvider) {
1266        return getSummary(context, null, state, isEphemeral, passpointProvider);
1267    }
1268
1269    public static String convertToQuotedString(String string) {
1270        return "\"" + string + "\"";
1271    }
1272
1273    private static int getPskType(ScanResult result) {
1274        boolean wpa = result.capabilities.contains("WPA-PSK");
1275        boolean wpa2 = result.capabilities.contains("WPA2-PSK");
1276        if (wpa2 && wpa) {
1277            return PSK_WPA_WPA2;
1278        } else if (wpa2) {
1279            return PSK_WPA2;
1280        } else if (wpa) {
1281            return PSK_WPA;
1282        } else {
1283            Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
1284            return PSK_UNKNOWN;
1285        }
1286    }
1287
1288    private static int getSecurity(ScanResult result) {
1289        if (result.capabilities.contains("WEP")) {
1290            return SECURITY_WEP;
1291        } else if (result.capabilities.contains("PSK")) {
1292            return SECURITY_PSK;
1293        } else if (result.capabilities.contains("EAP")) {
1294            return SECURITY_EAP;
1295        }
1296        return SECURITY_NONE;
1297    }
1298
1299    static int getSecurity(WifiConfiguration config) {
1300        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
1301            return SECURITY_PSK;
1302        }
1303        if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
1304                config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
1305            return SECURITY_EAP;
1306        }
1307        return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
1308    }
1309
1310    public static String securityToString(int security, int pskType) {
1311        if (security == SECURITY_WEP) {
1312            return "WEP";
1313        } else if (security == SECURITY_PSK) {
1314            if (pskType == PSK_WPA) {
1315                return "WPA";
1316            } else if (pskType == PSK_WPA2) {
1317                return "WPA2";
1318            } else if (pskType == PSK_WPA_WPA2) {
1319                return "WPA_WPA2";
1320            }
1321            return "PSK";
1322        } else if (security == SECURITY_EAP) {
1323            return "EAP";
1324        }
1325        return "NONE";
1326    }
1327
1328    static String removeDoubleQuotes(String string) {
1329        if (TextUtils.isEmpty(string)) {
1330            return "";
1331        }
1332        int length = string.length();
1333        if ((length > 1) && (string.charAt(0) == '"')
1334                && (string.charAt(length - 1) == '"')) {
1335            return string.substring(1, length - 1);
1336        }
1337        return string;
1338    }
1339
1340    /**
1341     * Callbacks relaying changes to the AccessPoint representation.
1342     *
1343     * <p>All methods are invoked on the Main Thread.
1344     */
1345    public interface AccessPointListener {
1346        /**
1347         * Indicates a change to the externally visible state of the AccessPoint trigger by an
1348         * update of ScanResults, saved configuration state, connection state, or score
1349         * (labels/metered) state.
1350         *
1351         * <p>Clients should refresh their view of the AccessPoint to match the updated state when
1352         * this is invoked. Overall this method is extraneous if clients are listening to
1353         * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks.
1354         *
1355         * <p>Examples of changes include signal strength, connection state, speed label, and
1356         * generally anything that would impact the summary string.
1357         *
1358         * @param accessPoint The accessPoint object the listener was registered on which has
1359         *                    changed
1360         */
1361        @MainThread void onAccessPointChanged(AccessPoint accessPoint);
1362
1363        /**
1364         * Indicates the "wifi pie signal level" has changed, retrieved via calls to
1365         * {@link AccessPoint#getLevel()}.
1366         *
1367         * <p>This call is a subset of {@link #onAccessPointChanged(AccessPoint)} , hence is also
1368         * extraneous if the client is already reacting to that or the
1369         * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks.
1370         *
1371         * @param accessPoint The accessPoint object the listener was registered on whose level has
1372         *                    changed
1373         */
1374        @MainThread void onLevelChanged(AccessPoint accessPoint);
1375    }
1376
1377    private static boolean isVerboseLoggingEnabled() {
1378        return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
1379    }
1380}
1381