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 
17 package com.android.settingslib.wifi;
18 
19 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
20 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED;
21 
22 import android.annotation.IntDef;
23 import android.annotation.MainThread;
24 import android.annotation.Nullable;
25 import android.app.AppGlobals;
26 import android.content.Context;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.IPackageManager;
29 import android.content.pm.PackageManager;
30 import android.net.ConnectivityManager;
31 import android.net.NetworkCapabilities;
32 import android.net.NetworkInfo;
33 import android.net.NetworkInfo.DetailedState;
34 import android.net.NetworkInfo.State;
35 import android.net.NetworkKey;
36 import android.net.NetworkScoreManager;
37 import android.net.NetworkScorerAppData;
38 import android.net.ScoredNetwork;
39 import android.net.wifi.ScanResult;
40 import android.net.wifi.WifiConfiguration;
41 import android.net.wifi.WifiConfiguration.KeyMgmt;
42 import android.net.wifi.WifiInfo;
43 import android.net.wifi.WifiManager;
44 import android.net.wifi.WifiNetworkScoreCache;
45 import android.net.wifi.hotspot2.OsuProvider;
46 import android.net.wifi.hotspot2.PasspointConfiguration;
47 import android.net.wifi.hotspot2.ProvisioningCallback;
48 import android.os.Bundle;
49 import android.os.Parcelable;
50 import android.os.RemoteException;
51 import android.os.SystemClock;
52 import android.os.UserHandle;
53 import android.provider.Settings;
54 import android.text.TextUtils;
55 import android.util.ArraySet;
56 import android.util.Log;
57 import android.util.Pair;
58 
59 import androidx.annotation.GuardedBy;
60 import androidx.annotation.NonNull;
61 
62 import com.android.internal.annotations.VisibleForTesting;
63 import com.android.internal.util.CollectionUtils;
64 import com.android.settingslib.R;
65 import com.android.settingslib.utils.ThreadUtils;
66 
67 import java.lang.annotation.Retention;
68 import java.lang.annotation.RetentionPolicy;
69 import java.util.ArrayList;
70 import java.util.Collection;
71 import java.util.Collections;
72 import java.util.HashMap;
73 import java.util.Iterator;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Set;
77 import java.util.concurrent.atomic.AtomicInteger;
78 
79 /**
80  * Represents a selectable Wifi Network for use in various wifi selection menus backed by
81  * {@link WifiTracker}.
82  *
83  * <p>An AccessPoint, which would be more fittingly named "WifiNetwork", is an aggregation of
84  * {@link ScanResult ScanResults} along with pertinent metadata (e.g. current connection info,
85  * network scores) required to successfully render the network to the user.
86  *
87  * @deprecated WifiTracker/AccessPoint is no longer supported, and will be removed in a future
88  * release. Clients that need a dynamic list of available wifi networks should migrate to one of the
89  * newer tracker classes,
90  * {@link com.android.wifitrackerlib.WifiPickerTracker},
91  * {@link com.android.wifitrackerlib.SavedNetworkTracker},
92  * {@link com.android.wifitrackerlib.NetworkDetailsTracker},
93  * in conjunction with {@link com.android.wifitrackerlib.WifiEntry} to represent each wifi network.
94  */
95 @Deprecated
96 public class AccessPoint implements Comparable<AccessPoint> {
97     static final String TAG = "SettingsLib.AccessPoint";
98 
99     /**
100      * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
101      */
102     public static final int LOWER_FREQ_24GHZ = 2400;
103 
104     /**
105      * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
106      */
107     public static final int HIGHER_FREQ_24GHZ = 2500;
108 
109     /**
110      * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
111      */
112     public static final int LOWER_FREQ_5GHZ = 4900;
113 
114     /**
115      * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
116      */
117     public static final int HIGHER_FREQ_5GHZ = 5900;
118 
119     /** The key which identifies this AccessPoint grouping. */
120     private String mKey;
121 
122     /**
123      * Synchronization lock for managing concurrency between main and worker threads.
124      *
125      * <p>This lock should be held for all modifications to {@link #mScanResults} and
126      * {@link #mExtraScanResults}.
127      */
128     private final Object mLock = new Object();
129 
130     @IntDef({Speed.NONE, Speed.SLOW, Speed.MODERATE, Speed.FAST, Speed.VERY_FAST})
131     @Retention(RetentionPolicy.SOURCE)
132     public @interface Speed {
133         /**
134          * Constant value representing an unlabeled / unscored network.
135          */
136         int NONE = 0;
137         /**
138          * Constant value representing a slow speed network connection.
139          */
140         int SLOW = 5;
141         /**
142          * Constant value representing a medium speed network connection.
143          */
144         int MODERATE = 10;
145         /**
146          * Constant value representing a fast speed network connection.
147          */
148         int FAST = 20;
149         /**
150          * Constant value representing a very fast speed network connection.
151          */
152         int VERY_FAST = 30;
153     }
154 
155     @IntDef({PasspointConfigurationVersion.INVALID,
156             PasspointConfigurationVersion.NO_OSU_PROVISIONED,
157             PasspointConfigurationVersion.OSU_PROVISIONED})
158     @Retention(RetentionPolicy.SOURCE)
159     public @interface PasspointConfigurationVersion {
160         int INVALID = 0;
161         int NO_OSU_PROVISIONED = 1; // R1.
162         int OSU_PROVISIONED = 2;    // R2 or R3.
163     }
164 
165     /** The underlying set of scan results comprising this AccessPoint. */
166     @GuardedBy("mLock")
167     private final ArraySet<ScanResult> mScanResults = new ArraySet<>();
168 
169     /**
170      * Extra set of unused scan results corresponding to this AccessPoint for verbose logging
171      * purposes, such as a set of Passpoint roaming scan results when home scans are available.
172      */
173     @GuardedBy("mLock")
174     private final ArraySet<ScanResult> mExtraScanResults = new ArraySet<>();
175 
176     /**
177      * Map of BSSIDs to scored networks for individual bssids.
178      *
179      * <p>This cache should not be evicted with scan results, as the values here are used to
180      * generate a fallback in the absence of scores for the visible APs.
181      */
182     private final Map<String, TimestampedScoredNetwork> mScoredNetworkCache = new HashMap<>();
183 
184     static final String KEY_NETWORKINFO = "key_networkinfo";
185     static final String KEY_WIFIINFO = "key_wifiinfo";
186     static final String KEY_SSID = "key_ssid";
187     static final String KEY_SECURITY = "key_security";
188     static final String KEY_SPEED = "key_speed";
189     static final String KEY_PSKTYPE = "key_psktype";
190     static final String KEY_SCANRESULTS = "key_scanresults";
191     static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache";
192     static final String KEY_CONFIG = "key_config";
193     static final String KEY_PASSPOINT_UNIQUE_ID = "key_passpoint_unique_id";
194     static final String KEY_FQDN = "key_fqdn";
195     static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
196     static final String KEY_EAPTYPE = "eap_psktype";
197     static final String KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS  =
198             "key_subscription_expiration_time_in_millis";
199     static final String KEY_PASSPOINT_CONFIGURATION_VERSION = "key_passpoint_configuration_version";
200     static final String KEY_IS_PSK_SAE_TRANSITION_MODE = "key_is_psk_sae_transition_mode";
201     static final String KEY_IS_OWE_TRANSITION_MODE = "key_is_owe_transition_mode";
202     static final AtomicInteger sLastId = new AtomicInteger(0);
203 
204     /*
205      * NOTE: These constants for security and PSK types are saved to the bundle in saveWifiState,
206      * and sent across IPC. The numeric values should remain stable, otherwise the changes will need
207      * to be synced with other unbundled users of this library.
208      */
209     public static final int SECURITY_NONE = 0;
210     public static final int SECURITY_WEP = 1;
211     public static final int SECURITY_PSK = 2;
212     public static final int SECURITY_EAP = 3;
213     public static final int SECURITY_OWE = 4;
214     public static final int SECURITY_SAE = 5;
215     public static final int SECURITY_EAP_SUITE_B = 6;
216     public static final int SECURITY_MAX_VAL = 7; // Has to be the last
217 
218     private static final int PSK_UNKNOWN = 0;
219     private static final int PSK_WPA = 1;
220     private static final int PSK_WPA2 = 2;
221     private static final int PSK_WPA_WPA2 = 3;
222 
223     private static final int EAP_UNKNOWN = 0;
224     private static final int EAP_WPA = 1; // WPA-EAP
225     private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP
226 
227     public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
228 
229     public static final String KEY_PREFIX_AP = "AP:";
230     public static final String KEY_PREFIX_PASSPOINT_UNIQUE_ID = "PASSPOINT:";
231     public static final String KEY_PREFIX_OSU = "OSU:";
232 
233     private final Context mContext;
234 
235     private WifiManager mWifiManager;
236     private WifiManager.ActionListener mConnectListener;
237 
238     private String ssid;
239     private String bssid;
240     private int security;
241     private int networkId = WifiConfiguration.INVALID_NETWORK_ID;
242 
243     private int pskType = PSK_UNKNOWN;
244     private int mEapType = EAP_UNKNOWN;
245 
246     private WifiConfiguration mConfig;
247 
248     private int mRssi = UNREACHABLE_RSSI;
249 
250     private WifiInfo mInfo;
251     private NetworkInfo mNetworkInfo;
252     AccessPointListener mAccessPointListener;
253 
254     private Object mTag;
255 
256     @Speed private int mSpeed = Speed.NONE;
257     private boolean mIsScoredNetworkMetered = false;
258 
259     /**
260      * Information associated with the {@link PasspointConfiguration}.  Only maintaining
261      * the relevant info to preserve spaces.
262      */
263     private String mPasspointUniqueId;
264     private String mFqdn;
265     private String mProviderFriendlyName;
266     private boolean mIsRoaming = false;
267     private long mSubscriptionExpirationTimeInMillis;
268     @PasspointConfigurationVersion private int mPasspointConfigurationVersion =
269             PasspointConfigurationVersion.INVALID;
270 
271     private OsuProvider mOsuProvider;
272 
273     private String mOsuStatus;
274     private String mOsuFailure;
275     private boolean mOsuProvisioningComplete = false;
276 
277     private boolean mIsPskSaeTransitionMode = false;
278     private boolean mIsOweTransitionMode = false;
279 
AccessPoint(Context context, Bundle savedState)280     public AccessPoint(Context context, Bundle savedState) {
281         mContext = context;
282 
283         if (savedState.containsKey(KEY_CONFIG)) {
284             mConfig = savedState.getParcelable(KEY_CONFIG);
285         }
286         if (mConfig != null) {
287             loadConfig(mConfig);
288         }
289         if (savedState.containsKey(KEY_SSID)) {
290             ssid = savedState.getString(KEY_SSID);
291         }
292         if (savedState.containsKey(KEY_SECURITY)) {
293             security = savedState.getInt(KEY_SECURITY);
294         }
295         if (savedState.containsKey(KEY_SPEED)) {
296             mSpeed = savedState.getInt(KEY_SPEED);
297         }
298         if (savedState.containsKey(KEY_PSKTYPE)) {
299             pskType = savedState.getInt(KEY_PSKTYPE);
300         }
301         if (savedState.containsKey(KEY_EAPTYPE)) {
302             mEapType = savedState.getInt(KEY_EAPTYPE);
303         }
304         mInfo = savedState.getParcelable(KEY_WIFIINFO);
305         if (savedState.containsKey(KEY_NETWORKINFO)) {
306             mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
307         }
308         if (savedState.containsKey(KEY_SCANRESULTS)) {
309             Parcelable[] scanResults = savedState.getParcelableArray(KEY_SCANRESULTS);
310             mScanResults.clear();
311             for (Parcelable result : scanResults) {
312                 mScanResults.add((ScanResult) result);
313             }
314         }
315         if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) {
316             ArrayList<TimestampedScoredNetwork> scoredNetworkArrayList =
317                     savedState.getParcelableArrayList(KEY_SCOREDNETWORKCACHE);
318             for (TimestampedScoredNetwork timedScore : scoredNetworkArrayList) {
319                 mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore);
320             }
321         }
322         if (savedState.containsKey(KEY_PASSPOINT_UNIQUE_ID)) {
323             mPasspointUniqueId = savedState.getString(KEY_PASSPOINT_UNIQUE_ID);
324         }
325         if (savedState.containsKey(KEY_FQDN)) {
326             mFqdn = savedState.getString(KEY_FQDN);
327         }
328         if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
329             mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
330         }
331         if (savedState.containsKey(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS)) {
332             mSubscriptionExpirationTimeInMillis =
333                     savedState.getLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS);
334         }
335         if (savedState.containsKey(KEY_PASSPOINT_CONFIGURATION_VERSION)) {
336             mPasspointConfigurationVersion = savedState.getInt(KEY_PASSPOINT_CONFIGURATION_VERSION);
337         }
338         if (savedState.containsKey(KEY_IS_PSK_SAE_TRANSITION_MODE)) {
339             mIsPskSaeTransitionMode = savedState.getBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE);
340         }
341         if (savedState.containsKey(KEY_IS_OWE_TRANSITION_MODE)) {
342             mIsOweTransitionMode = savedState.getBoolean(KEY_IS_OWE_TRANSITION_MODE);
343         }
344 
345         update(mConfig, mInfo, mNetworkInfo);
346 
347         // Calculate required fields
348         updateKey();
349         updateBestRssiInfo();
350     }
351 
352     /**
353      * Creates an AccessPoint with only a WifiConfiguration. This is used for the saved networks
354      * page.
355      */
AccessPoint(Context context, WifiConfiguration config)356     public AccessPoint(Context context, WifiConfiguration config) {
357         mContext = context;
358         loadConfig(config);
359         updateKey();
360     }
361 
362     /**
363      * Initialize an AccessPoint object for a {@link PasspointConfiguration}.  This is mainly
364      * used by "Saved Networks" page for managing the saved {@link PasspointConfiguration}.
365      */
AccessPoint(Context context, PasspointConfiguration config)366     public AccessPoint(Context context, PasspointConfiguration config) {
367         mContext = context;
368         mPasspointUniqueId = config.getUniqueId();
369         mFqdn = config.getHomeSp().getFqdn();
370         mProviderFriendlyName = config.getHomeSp().getFriendlyName();
371         mSubscriptionExpirationTimeInMillis = config.getSubscriptionExpirationTimeMillis();
372         if (config.isOsuProvisioned()) {
373             mPasspointConfigurationVersion = PasspointConfigurationVersion.OSU_PROVISIONED;
374         } else {
375             mPasspointConfigurationVersion = PasspointConfigurationVersion.NO_OSU_PROVISIONED;
376         }
377         updateKey();
378     }
379 
380     /**
381      * Initialize an AccessPoint object for a Passpoint network.
382      */
AccessPoint(@onNull Context context, @NonNull WifiConfiguration config, @Nullable Collection<ScanResult> homeScans, @Nullable Collection<ScanResult> roamingScans)383     public AccessPoint(@NonNull Context context, @NonNull WifiConfiguration config,
384             @Nullable Collection<ScanResult> homeScans,
385             @Nullable Collection<ScanResult> roamingScans) {
386         mContext = context;
387         networkId = config.networkId;
388         mConfig = config;
389         mPasspointUniqueId = config.getKey();
390         mFqdn = config.FQDN;
391         setScanResultsPasspoint(homeScans, roamingScans);
392         updateKey();
393     }
394 
395     /**
396      * Initialize an AccessPoint object for a Passpoint OSU Provider.
397      */
AccessPoint(@onNull Context context, @NonNull OsuProvider provider, @NonNull Collection<ScanResult> results)398     public AccessPoint(@NonNull Context context, @NonNull OsuProvider provider,
399             @NonNull Collection<ScanResult> results) {
400         mContext = context;
401         mOsuProvider = provider;
402         setScanResults(results);
403         updateKey();
404     }
405 
AccessPoint(Context context, Collection<ScanResult> results)406     AccessPoint(Context context, Collection<ScanResult> results) {
407         mContext = context;
408         setScanResults(results);
409         updateKey();
410     }
411 
loadConfig(WifiConfiguration config)412     @VisibleForTesting void loadConfig(WifiConfiguration config) {
413         ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
414         bssid = config.BSSID;
415         security = getSecurity(config);
416         networkId = config.networkId;
417         mConfig = config;
418     }
419 
420     /** Updates {@link #mKey} and should only called upon object creation/initialization. */
updateKey()421     private void updateKey() {
422         // TODO(sghuman): Consolidate Key logic on ScanResultMatchInfo
423         if (isPasspoint()) {
424             mKey = getKey(mConfig);
425         } else if (isPasspointConfig()) {
426             mKey = getKey(mPasspointUniqueId);
427         } else if (isOsuProvider()) {
428             mKey = getKey(mOsuProvider);
429         } else { // Non-Passpoint AP
430             mKey = getKey(getSsidStr(), getBssid(), getSecurity());
431         }
432     }
433 
434     /**
435     * Returns a negative integer, zero, or a positive integer if this AccessPoint is less than,
436     * equal to, or greater than the other AccessPoint.
437     *
438     * Sort order rules for AccessPoints:
439     *   1. Active before inactive
440     *   2. Reachable before unreachable
441     *   3. Saved before unsaved
442     *   4. Network speed value
443     *   5. Stronger signal before weaker signal
444     *   6. SSID alphabetically
445     *
446     * Note that AccessPoints with a signal are usually also Reachable,
447     * and will thus appear before unreachable saved AccessPoints.
448     */
449     @Override
compareTo(@onNull AccessPoint other)450     public int compareTo(@NonNull AccessPoint other) {
451         // Active one goes first.
452         if (isActive() && !other.isActive()) return -1;
453         if (!isActive() && other.isActive()) return 1;
454 
455         // Reachable one goes before unreachable one.
456         if (isReachable() && !other.isReachable()) return -1;
457         if (!isReachable() && other.isReachable()) return 1;
458 
459         // Configured (saved) one goes before unconfigured one.
460         if (isSaved() && !other.isSaved()) return -1;
461         if (!isSaved() && other.isSaved()) return 1;
462 
463         // Faster speeds go before slower speeds - but only if visible change in speed label
464         if (getSpeed() != other.getSpeed()) {
465             return other.getSpeed() - getSpeed();
466         }
467 
468         WifiManager wifiManager = getWifiManager();
469         // Sort by signal strength, bucketed by level
470         int difference = wifiManager.calculateSignalLevel(other.mRssi)
471                 - wifiManager.calculateSignalLevel(mRssi);
472         if (difference != 0) {
473             return difference;
474         }
475 
476         // Sort by title.
477         difference = getTitle().compareToIgnoreCase(other.getTitle());
478         if (difference != 0) {
479             return difference;
480         }
481 
482         // Do a case sensitive comparison to distinguish SSIDs that differ in case only
483         return getSsidStr().compareTo(other.getSsidStr());
484     }
485 
486     @Override
equals(Object other)487     public boolean equals(Object other) {
488         if (!(other instanceof AccessPoint)) return false;
489         return (this.compareTo((AccessPoint) other) == 0);
490     }
491 
492     @Override
hashCode()493     public int hashCode() {
494         int result = 0;
495         if (mInfo != null) result += 13 * mInfo.hashCode();
496         result += 19 * mRssi;
497         result += 23 * networkId;
498         result += 29 * ssid.hashCode();
499         return result;
500     }
501 
502     @Override
toString()503     public String toString() {
504         StringBuilder builder = new StringBuilder().append("AccessPoint(")
505                 .append(ssid);
506         if (bssid != null) {
507             builder.append(":").append(bssid);
508         }
509         if (isSaved()) {
510             builder.append(',').append("saved");
511         }
512         if (isActive()) {
513             builder.append(',').append("active");
514         }
515         if (isEphemeral()) {
516             builder.append(',').append("ephemeral");
517         }
518         if (isConnectable()) {
519             builder.append(',').append("connectable");
520         }
521         if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) {
522             builder.append(',').append(securityToString(security, pskType));
523         }
524         builder.append(",level=").append(getLevel());
525         if (mSpeed != Speed.NONE) {
526             builder.append(",speed=").append(mSpeed);
527         }
528         builder.append(",metered=").append(isMetered());
529 
530         if (isVerboseLoggingEnabled()) {
531             builder.append(",rssi=").append(mRssi);
532             synchronized (mLock) {
533                 builder.append(",scan cache size=").append(mScanResults.size()
534                         + mExtraScanResults.size());
535             }
536         }
537 
538         return builder.append(')').toString();
539     }
540 
541     /**
542      * Updates the AccessPoint rankingScore, metering, and speed, returning true if the data has
543      * changed.
544      *
545      * @param scoreCache The score cache to use to retrieve scores
546      * @param scoringUiEnabled Whether to show scoring and badging UI
547      * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when
548      *         generating speed labels
549      */
update( WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled, long maxScoreCacheAgeMillis)550     boolean update(
551             WifiNetworkScoreCache scoreCache,
552             boolean scoringUiEnabled,
553             long maxScoreCacheAgeMillis) {
554         boolean scoreChanged = false;
555         if (scoringUiEnabled) {
556             scoreChanged = updateScores(scoreCache, maxScoreCacheAgeMillis);
557         }
558         return updateMetered(scoreCache) || scoreChanged;
559     }
560 
561     /**
562      * Updates the AccessPoint rankingScore and speed, returning true if the data has changed.
563      *
564      * <p>Any cached {@link TimestampedScoredNetwork} objects older than the given max age in millis
565      * will be removed when this method is invoked.
566      *
567      * <p>Precondition: {@link #mRssi} is up to date before invoking this method.
568      *
569      * @param scoreCache The score cache to use to retrieve scores
570      * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when
571      *         generating speed labels
572      *
573      * @return true if the set speed has changed
574      */
updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis)575     private boolean updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis) {
576         long nowMillis = SystemClock.elapsedRealtime();
577         synchronized (mLock) {
578             for (ScanResult result : mScanResults) {
579                 ScoredNetwork score = scoreCache.getScoredNetwork(result);
580                 if (score == null) {
581                     continue;
582                 }
583                 TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
584                 if (timedScore == null) {
585                     mScoredNetworkCache.put(
586                             result.BSSID, new TimestampedScoredNetwork(score, nowMillis));
587                 } else {
588                     // Update data since the has been seen in the score cache
589                     timedScore.update(score, nowMillis);
590                 }
591             }
592         }
593 
594         // Remove old cached networks
595         long evictionCutoff = nowMillis - maxScoreCacheAgeMillis;
596         Iterator<TimestampedScoredNetwork> iterator = mScoredNetworkCache.values().iterator();
597         iterator.forEachRemaining(timestampedScoredNetwork -> {
598             if (timestampedScoredNetwork.getUpdatedTimestampMillis() < evictionCutoff) {
599                 iterator.remove();
600             }
601         });
602 
603         return updateSpeed();
604     }
605 
606     /**
607      * Updates the internal speed, returning true if the update resulted in a speed label change.
608      */
updateSpeed()609     private boolean updateSpeed() {
610         int oldSpeed = mSpeed;
611         mSpeed = generateAverageSpeedForSsid();
612 
613         boolean changed = oldSpeed != mSpeed;
614         if(isVerboseLoggingEnabled() && changed) {
615             Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed));
616         }
617         return changed;
618     }
619 
620     /** Creates a speed value for the current {@link #mRssi} by averaging all non zero badges. */
generateAverageSpeedForSsid()621     @Speed private int generateAverageSpeedForSsid() {
622         if (mScoredNetworkCache.isEmpty()) {
623             return Speed.NONE;
624         }
625 
626         if (Log.isLoggable(TAG, Log.DEBUG)) {
627             Log.d(TAG, String.format("Generating fallbackspeed for %s using cache: %s",
628                     getSsidStr(), mScoredNetworkCache));
629         }
630 
631         // TODO(b/63073866): If flickering issues persist, consider mapping using getLevel rather
632         // than specific rssi value so score doesn't change without a visible wifi bar change. This
633         // issue is likely to be more evident for the active AP whose RSSI value is not half-lifed.
634 
635         int count = 0;
636         int totalSpeed = 0;
637         for (TimestampedScoredNetwork timedScore : mScoredNetworkCache.values()) {
638             int speed = timedScore.getScore().calculateBadge(mRssi);
639             if (speed != Speed.NONE) {
640                 count++;
641                 totalSpeed += speed;
642             }
643         }
644         int speed = count == 0 ? Speed.NONE : totalSpeed / count;
645         if (isVerboseLoggingEnabled()) {
646             Log.i(TAG, String.format("%s generated fallback speed is: %d", getSsidStr(), speed));
647         }
648         return roundToClosestSpeedEnum(speed);
649     }
650 
651     /**
652      * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning
653      * true if the metering changed.
654      */
updateMetered(WifiNetworkScoreCache scoreCache)655     private boolean updateMetered(WifiNetworkScoreCache scoreCache) {
656         boolean oldMetering = mIsScoredNetworkMetered;
657         mIsScoredNetworkMetered = false;
658 
659         if (isActive() && mInfo != null) {
660             NetworkKey key = NetworkKey.createFromWifiInfo(mInfo);
661             ScoredNetwork score = scoreCache.getScoredNetwork(key);
662             if (score != null) {
663                 mIsScoredNetworkMetered |= score.meteredHint;
664             }
665         } else {
666             synchronized (mLock) {
667                 for (ScanResult result : mScanResults) {
668                     ScoredNetwork score = scoreCache.getScoredNetwork(result);
669                     if (score == null) {
670                         continue;
671                     }
672                     mIsScoredNetworkMetered |= score.meteredHint;
673                 }
674             }
675         }
676         return oldMetering != mIsScoredNetworkMetered;
677     }
678 
679     /**
680      * Generates an AccessPoint key for a given scan result
681      *
682      * @param context
683      * @param result Scan result
684      * @return AccessPoint key
685      */
getKey(Context context, ScanResult result)686     public static String getKey(Context context, ScanResult result) {
687         return getKey(result.SSID, result.BSSID, getSecurity(context, result));
688     }
689 
690     /**
691      * Returns the AccessPoint key for a WifiConfiguration.
692      * This will return a special Passpoint key if the config is for Passpoint.
693      */
getKey(WifiConfiguration config)694     public static String getKey(WifiConfiguration config) {
695         if (config.isPasspoint()) {
696             return getKey(config.getKey());
697         } else {
698             return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config));
699         }
700     }
701 
702     /**
703      * Returns the AccessPoint key corresponding to a Passpoint network by its unique identifier.
704      */
getKey(String passpointUniqueId)705     public static String getKey(String passpointUniqueId) {
706         return new StringBuilder()
707                 .append(KEY_PREFIX_PASSPOINT_UNIQUE_ID)
708                 .append(passpointUniqueId).toString();
709     }
710 
711     /**
712      * Returns the AccessPoint key corresponding to the OsuProvider.
713      */
getKey(OsuProvider provider)714     public static String getKey(OsuProvider provider) {
715         return new StringBuilder()
716                 .append(KEY_PREFIX_OSU)
717                 .append(provider.getFriendlyName())
718                 .append(',')
719                 .append(provider.getServerUri()).toString();
720     }
721 
722     /**
723      * Returns the AccessPoint key for a normal non-Passpoint network by ssid/bssid and security.
724      */
getKey(String ssid, String bssid, int security)725     private static String getKey(String ssid, String bssid, int security) {
726         StringBuilder builder = new StringBuilder();
727         builder.append(KEY_PREFIX_AP);
728         if (TextUtils.isEmpty(ssid)) {
729             builder.append(bssid);
730         } else {
731             builder.append(ssid);
732         }
733         builder.append(',').append(security);
734         return builder.toString();
735     }
736 
getKey()737     public String getKey() {
738         return mKey;
739     }
740 
741     /**
742      * Determines if the other AccessPoint represents the same network as this AccessPoint
743      */
matches(AccessPoint other)744     public boolean matches(AccessPoint other) {
745         if (isPasspoint() || isPasspointConfig() || isOsuProvider()) {
746             return getKey().equals(other.getKey());
747         }
748 
749         if (!isSameSsidOrBssid(other)) {
750             return false;
751         }
752 
753         final int otherApSecurity = other.getSecurity();
754         if (mIsPskSaeTransitionMode) {
755             if (otherApSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) {
756                 return true;
757             } else if (otherApSecurity == SECURITY_PSK) {
758                 return true;
759             }
760         } else {
761             if ((security == SECURITY_SAE || security == SECURITY_PSK)
762                     && other.isPskSaeTransitionMode()) {
763                 return true;
764             }
765         }
766 
767         if (mIsOweTransitionMode) {
768             if (otherApSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
769                 return true;
770             } else if (otherApSecurity == SECURITY_NONE) {
771                 return true;
772             }
773         } else {
774             if ((security == SECURITY_OWE || security == SECURITY_NONE)
775                     && other.isOweTransitionMode()) {
776                 return true;
777             }
778         }
779 
780         return security == other.getSecurity();
781     }
782 
matches(WifiConfiguration config)783     public boolean matches(WifiConfiguration config) {
784         if (config.isPasspoint()) {
785             return (isPasspoint() && config.getKey().equals(mConfig.getKey()));
786         }
787 
788         if (!ssid.equals(removeDoubleQuotes(config.SSID))
789                 || (mConfig != null && mConfig.shared != config.shared)) {
790             return false;
791         }
792 
793         final int configSecurity = getSecurity(config);
794         if (mIsPskSaeTransitionMode) {
795             if (configSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) {
796                 return true;
797             } else if (configSecurity == SECURITY_PSK) {
798                 return true;
799             }
800         }
801 
802         if (mIsOweTransitionMode) {
803             if (configSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
804                 return true;
805             } else if (configSecurity == SECURITY_NONE) {
806                 return true;
807             }
808         }
809 
810         return security == getSecurity(config);
811     }
812 
matches(WifiConfiguration config, WifiInfo wifiInfo)813     private boolean matches(WifiConfiguration config, WifiInfo wifiInfo) {
814         if (config == null || wifiInfo == null) {
815             return false;
816         }
817         if (!config.isPasspoint() && !isSameSsidOrBssid(wifiInfo)) {
818             return false;
819         }
820         return matches(config);
821     }
822 
823     @VisibleForTesting
matches(ScanResult scanResult)824     boolean matches(ScanResult scanResult) {
825         if (scanResult == null) {
826             return false;
827         }
828         if (isPasspoint() || isOsuProvider()) {
829             throw new IllegalStateException("Should not matches a Passpoint by ScanResult");
830         }
831 
832         if (!isSameSsidOrBssid(scanResult)) {
833             return false;
834         }
835 
836         if (mIsPskSaeTransitionMode) {
837             if (scanResult.capabilities.contains("SAE")
838                     && getWifiManager().isWpa3SaeSupported()) {
839                 return true;
840             } else if (scanResult.capabilities.contains("PSK")) {
841                 return true;
842             }
843         } else {
844             if ((security == SECURITY_SAE || security == SECURITY_PSK)
845                     && AccessPoint.isPskSaeTransitionMode(scanResult)) {
846                 return true;
847             }
848         }
849 
850         if (mIsOweTransitionMode) {
851             final int scanResultSccurity = getSecurity(mContext, scanResult);
852             if (scanResultSccurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
853                 return true;
854             } else if (scanResultSccurity == SECURITY_NONE) {
855                 return true;
856             }
857         } else {
858             if ((security == SECURITY_OWE || security == SECURITY_NONE)
859                     && AccessPoint.isOweTransitionMode(scanResult)) {
860                 return true;
861             }
862         }
863 
864         return security == getSecurity(mContext, scanResult);
865     }
866 
getConfig()867     public WifiConfiguration getConfig() {
868         return mConfig;
869     }
870 
getPasspointFqdn()871     public String getPasspointFqdn() {
872         return mFqdn;
873     }
874 
clearConfig()875     public void clearConfig() {
876         mConfig = null;
877         networkId = WifiConfiguration.INVALID_NETWORK_ID;
878     }
879 
getInfo()880     public WifiInfo getInfo() {
881         return mInfo;
882     }
883 
884     /**
885      * Returns the number of levels to show for a Wifi icon, from 0 to
886      * {@link WifiManager#getMaxSignalLevel()}.
887      *
888      * <p>Use {@link #isReachable()} to determine if an AccessPoint is in range, as this method will
889      * always return at least 0.
890      */
getLevel()891     public int getLevel() {
892         return getWifiManager().calculateSignalLevel(mRssi);
893     }
894 
getRssi()895     public int getRssi() {
896         return mRssi;
897     }
898 
899     /**
900      * Returns the underlying scan result set.
901      *
902      * <p>Callers should not modify this set.
903      */
getScanResults()904     public Set<ScanResult> getScanResults() {
905         Set<ScanResult> allScans = new ArraySet<>();
906         synchronized (mLock) {
907             allScans.addAll(mScanResults);
908             allScans.addAll(mExtraScanResults);
909         }
910         return allScans;
911     }
912 
getScoredNetworkCache()913     public Map<String, TimestampedScoredNetwork> getScoredNetworkCache() {
914         return mScoredNetworkCache;
915     }
916 
917     /**
918      * Updates {@link #mRssi} and sets scan result information to that of the best RSSI scan result.
919      *
920      * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned.
921      * If the given AccessPoint is not active, a value will be calculated from previous scan
922      * results, returning the best RSSI for all matching AccessPoints averaged with the previous
923      * value. If the access point is not connected and there are no scan results, the rssi will be
924      * set to {@link #UNREACHABLE_RSSI}.
925      */
updateBestRssiInfo()926     private void updateBestRssiInfo() {
927         if (this.isActive()) {
928             return;
929         }
930 
931         ScanResult bestResult = null;
932         int bestRssi = UNREACHABLE_RSSI;
933         synchronized (mLock) {
934             for (ScanResult result : mScanResults) {
935                 if (result.level > bestRssi) {
936                     bestRssi = result.level;
937                     bestResult = result;
938                 }
939             }
940         }
941 
942         // Set the rssi to the average of the current rssi and the previous rssi.
943         if (bestRssi != UNREACHABLE_RSSI && mRssi != UNREACHABLE_RSSI) {
944             mRssi = (mRssi + bestRssi) / 2;
945         } else {
946             mRssi = bestRssi;
947         }
948 
949         if (bestResult != null) {
950             ssid = bestResult.SSID;
951             bssid = bestResult.BSSID;
952             security = getSecurity(mContext, bestResult);
953             if (security == SECURITY_PSK || security == SECURITY_SAE) {
954                 pskType = getPskType(bestResult);
955             }
956             if (security == SECURITY_EAP) {
957                 mEapType = getEapType(bestResult);
958             }
959 
960             mIsPskSaeTransitionMode = AccessPoint.isPskSaeTransitionMode(bestResult);
961             mIsOweTransitionMode = AccessPoint.isOweTransitionMode(bestResult);
962         }
963         // Update the config SSID of a Passpoint network to that of the best RSSI
964         if (isPasspoint()) {
965             mConfig.SSID = convertToQuotedString(ssid);
966         }
967     }
968 
969     /**
970      * Returns if the network should be considered metered.
971      */
isMetered()972     public boolean isMetered() {
973         return mIsScoredNetworkMetered
974                 || WifiConfiguration.isMetered(mConfig, mInfo);
975     }
976 
getNetworkInfo()977     public NetworkInfo getNetworkInfo() {
978         return mNetworkInfo;
979     }
980 
getSecurity()981     public int getSecurity() {
982         return security;
983     }
984 
getSecurityString(boolean concise)985     public String getSecurityString(boolean concise) {
986         Context context = mContext;
987         if (isPasspoint() || isPasspointConfig()) {
988             return concise ? context.getString(R.string.wifi_security_short_eap) :
989                 context.getString(R.string.wifi_security_eap);
990         }
991 
992         if (mIsPskSaeTransitionMode) {
993             return concise ? context.getString(R.string.wifi_security_short_psk_sae) :
994                     context.getString(R.string.wifi_security_psk_sae);
995         }
996         if (mIsOweTransitionMode) {
997             return concise ? context.getString(R.string.wifi_security_short_none_owe) :
998                     context.getString(R.string.wifi_security_none_owe);
999         }
1000 
1001         switch(security) {
1002             case SECURITY_EAP:
1003                 switch (mEapType) {
1004                     case EAP_WPA:
1005                         return concise ? context.getString(R.string.wifi_security_short_eap_wpa) :
1006                                 context.getString(R.string.wifi_security_eap_wpa);
1007                     case EAP_WPA2_WPA3:
1008                         return concise
1009                                 ? context.getString(R.string.wifi_security_short_eap_wpa2_wpa3) :
1010                                 context.getString(R.string.wifi_security_eap_wpa2_wpa3);
1011                     case EAP_UNKNOWN:
1012                     default:
1013                         return concise
1014                                 ? context.getString(R.string.wifi_security_short_eap) :
1015                                 context.getString(R.string.wifi_security_eap);
1016                 }
1017             case SECURITY_EAP_SUITE_B:
1018                 return concise ? context.getString(R.string.wifi_security_short_eap_suiteb) :
1019                         context.getString(R.string.wifi_security_eap_suiteb);
1020             case SECURITY_PSK:
1021                 switch (pskType) {
1022                     case PSK_WPA:
1023                         return concise ? context.getString(R.string.wifi_security_short_wpa) :
1024                             context.getString(R.string.wifi_security_wpa);
1025                     case PSK_WPA2:
1026                         return concise ? context.getString(R.string.wifi_security_short_wpa2) :
1027                             context.getString(R.string.wifi_security_wpa2);
1028                     case PSK_WPA_WPA2:
1029                         return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
1030                             context.getString(R.string.wifi_security_wpa_wpa2);
1031                     case PSK_UNKNOWN:
1032                     default:
1033                         return concise ? context.getString(R.string.wifi_security_short_psk_generic)
1034                                 : context.getString(R.string.wifi_security_psk_generic);
1035                 }
1036             case SECURITY_WEP:
1037                 return concise ? context.getString(R.string.wifi_security_short_wep) :
1038                     context.getString(R.string.wifi_security_wep);
1039             case SECURITY_SAE:
1040                 return concise ? context.getString(R.string.wifi_security_short_sae) :
1041                         context.getString(R.string.wifi_security_sae);
1042             case SECURITY_OWE:
1043                 return concise ? context.getString(R.string.wifi_security_short_owe) :
1044                     context.getString(R.string.wifi_security_owe);
1045             case SECURITY_NONE:
1046             default:
1047                 return concise ? "" : context.getString(R.string.wifi_security_none);
1048         }
1049     }
1050 
getSsidStr()1051     public String getSsidStr() {
1052         return ssid;
1053     }
1054 
getBssid()1055     public String getBssid() {
1056         return bssid;
1057     }
1058 
getSsid()1059     public CharSequence getSsid() {
1060         return ssid;
1061     }
1062 
1063     /**
1064      * Returns the name associated with the stored config.
1065      * @deprecated Please use {@link #getTitle()} instead to get the display name of an AccessPoint.
1066      */
1067     @Deprecated
getConfigName()1068     public String getConfigName() {
1069         if (mConfig != null && mConfig.isPasspoint()) {
1070             return mConfig.providerFriendlyName;
1071         } else if (mPasspointUniqueId != null) {
1072             return mProviderFriendlyName;
1073         } else {
1074             return ssid;
1075         }
1076     }
1077 
getDetailedState()1078     public DetailedState getDetailedState() {
1079         if (mNetworkInfo != null) {
1080             return mNetworkInfo.getDetailedState();
1081         }
1082         Log.w(TAG, "NetworkInfo is null, cannot return detailed state");
1083         return null;
1084     }
1085 
getSavedNetworkSummary()1086     public String getSavedNetworkSummary() {
1087         WifiConfiguration config = mConfig;
1088         if (config != null) {
1089             PackageManager pm = mContext.getPackageManager();
1090             String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID);
1091             int userId = UserHandle.getUserId(config.creatorUid);
1092             ApplicationInfo appInfo = null;
1093             if (config.creatorName != null && config.creatorName.equals(systemName)) {
1094                 appInfo = mContext.getApplicationInfo();
1095             } else {
1096                 try {
1097                     IPackageManager ipm = AppGlobals.getPackageManager();
1098                     appInfo = ipm.getApplicationInfo(config.creatorName, 0 /* flags */, userId);
1099                 } catch (RemoteException rex) {
1100                 }
1101             }
1102             if (appInfo != null &&
1103                     !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) &&
1104                     !appInfo.packageName.equals(
1105                     mContext.getString(R.string.certinstaller_package))) {
1106                 return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm));
1107             }
1108         }
1109 
1110         if (isPasspointConfigurationR1() && isExpired()) {
1111             return mContext.getString(R.string.wifi_passpoint_expired);
1112         }
1113         return "";
1114     }
1115 
1116     /**
1117      * Returns the display title for the AccessPoint, such as for an AccessPointPreference's title.
1118      */
getTitle()1119     public String getTitle() {
1120         if (isPasspoint()) {
1121             return mConfig.providerFriendlyName;
1122         } else if (isPasspointConfig()) {
1123             return mProviderFriendlyName;
1124         } else if (isOsuProvider()) {
1125             return mOsuProvider.getFriendlyName();
1126         } else {
1127             return getSsidStr();
1128         }
1129     }
1130 
getSummary()1131     public String getSummary() {
1132         return getSettingsSummary();
1133     }
1134 
getSettingsSummary()1135     public String getSettingsSummary() {
1136         return getSettingsSummary(false /*convertSavedAsDisconnected*/);
1137     }
1138 
1139     /**
1140      * Returns the summary for the AccessPoint.
1141      */
getSettingsSummary(boolean convertSavedAsDisconnected)1142     public String getSettingsSummary(boolean convertSavedAsDisconnected) {
1143         if (isPasspointConfigurationR1() && isExpired()) {
1144             return mContext.getString(R.string.wifi_passpoint_expired);
1145         }
1146 
1147         // Update to new summary
1148         StringBuilder summary = new StringBuilder();
1149 
1150         if (isOsuProvider()) {
1151             if (mOsuProvisioningComplete) {
1152                 summary.append(mContext.getString(R.string.osu_sign_up_complete));
1153             } else if (mOsuFailure != null) {
1154                 summary.append(mOsuFailure);
1155             } else if (mOsuStatus != null) {
1156                 summary.append(mOsuStatus);
1157             } else {
1158                 summary.append(mContext.getString(R.string.tap_to_sign_up));
1159             }
1160         } else if (isActive()) {
1161             summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
1162                     mInfo != null && mInfo.isEphemeral(),
1163                     mInfo != null ? mInfo.getRequestingPackageName() : null));
1164         } else { // not active
1165             if (mConfig != null && mConfig.hasNoInternetAccess()) {
1166                 int messageID =
1167                         mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus()
1168                                 == NETWORK_SELECTION_PERMANENTLY_DISABLED
1169                         ? R.string.wifi_no_internet_no_reconnect
1170                         : R.string.wifi_no_internet;
1171                 summary.append(mContext.getString(messageID));
1172             } else if (mConfig != null
1173                     && (mConfig.getNetworkSelectionStatus().getNetworkSelectionStatus()
1174                             != NETWORK_SELECTION_ENABLED)) {
1175                 WifiConfiguration.NetworkSelectionStatus networkStatus =
1176                         mConfig.getNetworkSelectionStatus();
1177                 switch (networkStatus.getNetworkSelectionDisableReason()) {
1178                     case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
1179                         summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
1180                         break;
1181                     case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
1182                         summary.append(mContext.getString(R.string.wifi_check_password_try_again));
1183                         break;
1184                     case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
1185                         summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
1186                         break;
1187                     case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
1188                         summary.append(mContext.getString(R.string.wifi_disabled_generic));
1189                         break;
1190                 }
1191             } else if (!isReachable()) { // Wifi out of range
1192                 summary.append(mContext.getString(R.string.wifi_not_in_range));
1193             } else { // In range, not disabled.
1194                 if (mConfig != null) { // Is saved network
1195                     // Last attempt to connect to this failed. Show reason why
1196                     switch (mConfig.getRecentFailureReason()) {
1197                         case WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA:
1198                             summary.append(mContext.getString(
1199                                     R.string.wifi_ap_unable_to_handle_new_sta));
1200                             break;
1201                         default:
1202                             if (convertSavedAsDisconnected) {
1203                                 // Disconnected
1204                                 summary.append(mContext.getString(R.string.wifi_disconnected));
1205                             } else {
1206                                 // "Saved"
1207                                 summary.append(mContext.getString(R.string.wifi_remembered));
1208                             }
1209                             break;
1210                     }
1211                 }
1212             }
1213         }
1214 
1215 
1216 
1217         if (isVerboseLoggingEnabled()) {
1218             summary.append(WifiUtils.buildLoggingSummary(this, mConfig));
1219         }
1220 
1221         if (mConfig != null && (WifiUtils.isMeteredOverridden(mConfig) || mConfig.meteredHint)) {
1222             return mContext.getResources().getString(
1223                     R.string.preference_summary_default_combination,
1224                     WifiUtils.getMeteredLabel(mContext, mConfig),
1225                     summary.toString());
1226         }
1227 
1228         // If Speed label and summary are both present, use the preference combination to combine
1229         // the two, else return the non-null one.
1230         if (getSpeedLabel() != null && summary.length() != 0) {
1231             return mContext.getResources().getString(
1232                     R.string.preference_summary_default_combination,
1233                     getSpeedLabel(),
1234                     summary.toString());
1235         } else if (getSpeedLabel() != null) {
1236             return getSpeedLabel();
1237         } else {
1238             return summary.toString();
1239         }
1240     }
1241 
1242     /**
1243      * Return whether this is the active connection.
1244      * For ephemeral connections (networkId is invalid), this returns false if the network is
1245      * disconnected.
1246      */
isActive()1247     public boolean isActive() {
1248         return mNetworkInfo != null &&
1249                 (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
1250                  mNetworkInfo.getState() != State.DISCONNECTED);
1251     }
1252 
isConnectable()1253     public boolean isConnectable() {
1254         return getLevel() != -1 && getDetailedState() == null;
1255     }
1256 
isEphemeral()1257     public boolean isEphemeral() {
1258         return mInfo != null && mInfo.isEphemeral() &&
1259                 mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED;
1260     }
1261 
1262     /**
1263      * Return true if this AccessPoint represents a Passpoint AP.
1264      */
isPasspoint()1265     public boolean isPasspoint() {
1266         return mConfig != null && mConfig.isPasspoint();
1267     }
1268 
1269     /**
1270      * Return true if this AccessPoint represents a Passpoint provider configuration.
1271      */
isPasspointConfig()1272     public boolean isPasspointConfig() {
1273         return mPasspointUniqueId != null && mConfig == null;
1274     }
1275 
1276     /**
1277      * Return true if this AccessPoint represents an OSU Provider.
1278      */
isOsuProvider()1279     public boolean isOsuProvider() {
1280         return mOsuProvider != null;
1281     }
1282 
1283     /**
1284      * Return true if this AccessPoint is expired.
1285      */
isExpired()1286     public boolean isExpired() {
1287         if (mSubscriptionExpirationTimeInMillis <= 0) {
1288             // Expiration time not specified.
1289             return false;
1290         } else {
1291             return System.currentTimeMillis() >= mSubscriptionExpirationTimeInMillis;
1292         }
1293     }
1294 
isPasspointConfigurationR1()1295     public boolean isPasspointConfigurationR1() {
1296         return mPasspointConfigurationVersion == PasspointConfigurationVersion.NO_OSU_PROVISIONED;
1297     }
1298 
1299     /**
1300      * Return true if {@link PasspointConfiguration#isOsuProvisioned} is true, this may refer to R2
1301      * or R3.
1302      */
isPasspointConfigurationOsuProvisioned()1303     public boolean isPasspointConfigurationOsuProvisioned() {
1304         return mPasspointConfigurationVersion == PasspointConfigurationVersion.OSU_PROVISIONED;
1305     }
1306 
1307     /**
1308      * Starts the OSU Provisioning flow.
1309      */
startOsuProvisioning(@ullable WifiManager.ActionListener connectListener)1310     public void startOsuProvisioning(@Nullable WifiManager.ActionListener connectListener) {
1311         mConnectListener = connectListener;
1312 
1313         getWifiManager().startSubscriptionProvisioning(
1314                 mOsuProvider,
1315                 mContext.getMainExecutor(),
1316                 new AccessPointProvisioningCallback()
1317         );
1318     }
1319 
1320     /**
1321      * Return whether the given {@link WifiInfo} is for this access point.
1322      * If the current AP does not have a network Id then the config is used to
1323      * match based on SSID and security.
1324      */
isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info)1325     private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
1326         if (info.isOsuAp() || mOsuStatus != null) {
1327             return (info.isOsuAp() && mOsuStatus != null);
1328         } else if (info.isPasspointAp() || isPasspoint()) {
1329             // TODO: Use TextUtils.equals(info.getPasspointUniqueId(), mConfig.getKey()) when API
1330             //  is available
1331             return (info.isPasspointAp() && isPasspoint()
1332                     && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN)
1333                     && TextUtils.equals(info.getPasspointProviderFriendlyName(),
1334                     mConfig.providerFriendlyName));
1335         }
1336 
1337         if (networkId != WifiConfiguration.INVALID_NETWORK_ID) {
1338             return networkId == info.getNetworkId();
1339         } else if (config != null) {
1340             return matches(config, info);
1341         } else {
1342             // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
1343             // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
1344             // TODO: Handle hex string SSIDs.
1345             return TextUtils.equals(removeDoubleQuotes(info.getSSID()), ssid);
1346         }
1347     }
1348 
isSaved()1349     public boolean isSaved() {
1350         return mConfig != null;
1351     }
1352 
getTag()1353     public Object getTag() {
1354         return mTag;
1355     }
1356 
setTag(Object tag)1357     public void setTag(Object tag) {
1358         mTag = tag;
1359     }
1360 
1361     /**
1362      * Generate and save a default wifiConfiguration with common values.
1363      * Can only be called for unsecured networks.
1364      */
generateOpenNetworkConfig()1365     public void generateOpenNetworkConfig() {
1366         if (!isOpenNetwork()) {
1367             throw new IllegalStateException();
1368         }
1369         if (mConfig != null)
1370             return;
1371         mConfig = new WifiConfiguration();
1372         mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
1373 
1374         if (security == SECURITY_NONE) {
1375             mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
1376         } else {
1377             mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
1378             mConfig.requirePmf = true;
1379         }
1380     }
1381 
saveWifiState(Bundle savedState)1382     public void saveWifiState(Bundle savedState) {
1383         if (ssid != null) savedState.putString(KEY_SSID, getSsidStr());
1384         savedState.putInt(KEY_SECURITY, security);
1385         savedState.putInt(KEY_SPEED, mSpeed);
1386         savedState.putInt(KEY_PSKTYPE, pskType);
1387         savedState.putInt(KEY_EAPTYPE, mEapType);
1388         if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
1389         savedState.putParcelable(KEY_WIFIINFO, mInfo);
1390         synchronized (mLock) {
1391             savedState.putParcelableArray(KEY_SCANRESULTS,
1392                     mScanResults.toArray(new Parcelable[mScanResults.size()
1393                             + mExtraScanResults.size()]));
1394         }
1395         savedState.putParcelableArrayList(KEY_SCOREDNETWORKCACHE,
1396                 new ArrayList<>(mScoredNetworkCache.values()));
1397         if (mNetworkInfo != null) {
1398             savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
1399         }
1400         if (mPasspointUniqueId != null) {
1401             savedState.putString(KEY_PASSPOINT_UNIQUE_ID, mPasspointUniqueId);
1402         }
1403         if (mFqdn != null) {
1404             savedState.putString(KEY_FQDN, mFqdn);
1405         }
1406         if (mProviderFriendlyName != null) {
1407             savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
1408         }
1409         savedState.putLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS,
1410                 mSubscriptionExpirationTimeInMillis);
1411         savedState.putInt(KEY_PASSPOINT_CONFIGURATION_VERSION, mPasspointConfigurationVersion);
1412         savedState.putBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE, mIsPskSaeTransitionMode);
1413         savedState.putBoolean(KEY_IS_OWE_TRANSITION_MODE, mIsOweTransitionMode);
1414     }
1415 
setListener(AccessPointListener listener)1416     public void setListener(AccessPointListener listener) {
1417         mAccessPointListener = listener;
1418     }
1419 
1420     /**
1421      * Sets {@link #mScanResults} to the given collection and updates info based on the best RSSI
1422      * scan result.
1423      *
1424      * @param scanResults a collection of scan results to add to the internal set
1425      */
setScanResults(Collection<ScanResult> scanResults)1426     void setScanResults(Collection<ScanResult> scanResults) {
1427         if (CollectionUtils.isEmpty(scanResults)) {
1428             Log.d(TAG, "Cannot set scan results to empty list");
1429             return;
1430         }
1431 
1432         // Validate scan results are for current AP only by matching SSID/BSSID
1433         // Passpoint networks are not bound to a specific SSID/BSSID, so skip this for passpoint.
1434         if (mKey != null && !isPasspoint() && !isOsuProvider()) {
1435             for (ScanResult result : scanResults) {
1436                 if (!matches(result)) {
1437                     Log.d(TAG, String.format(
1438                             "ScanResult %s\nkey of %s did not match current AP key %s",
1439                             result, getKey(mContext, result), mKey));
1440                     return;
1441                 }
1442             }
1443         }
1444 
1445         int oldLevel = getLevel();
1446         synchronized (mLock) {
1447             mScanResults.clear();
1448             mScanResults.addAll(scanResults);
1449         }
1450         updateBestRssiInfo();
1451         int newLevel = getLevel();
1452 
1453         // If newLevel is 0, there will be no displayed Preference since the AP is unreachable
1454         if (newLevel > 0 && newLevel != oldLevel) {
1455             // Only update labels on visible rssi changes
1456             updateSpeed();
1457             ThreadUtils.postOnMainThread(() -> {
1458                 if (mAccessPointListener != null) {
1459                     mAccessPointListener.onLevelChanged(this);
1460                 }
1461             });
1462 
1463         }
1464 
1465         ThreadUtils.postOnMainThread(() -> {
1466             if (mAccessPointListener != null) {
1467                 mAccessPointListener.onAccessPointChanged(this);
1468             }
1469         });
1470     }
1471 
1472     /**
1473      * Sets the internal scan result cache to the list of home scans.
1474      * If there are no home scans, then the roaming scan list is used, and the AccessPoint is
1475      * marked as roaming.
1476      */
setScanResultsPasspoint( @ullable Collection<ScanResult> homeScans, @Nullable Collection<ScanResult> roamingScans)1477     void setScanResultsPasspoint(
1478             @Nullable Collection<ScanResult> homeScans,
1479             @Nullable Collection<ScanResult> roamingScans) {
1480         synchronized (mLock) {
1481             mExtraScanResults.clear();
1482             if (!CollectionUtils.isEmpty(homeScans)) {
1483                 mIsRoaming = false;
1484                 if (!CollectionUtils.isEmpty(roamingScans)) {
1485                     mExtraScanResults.addAll(roamingScans);
1486                 }
1487                 setScanResults(homeScans);
1488             } else if (!CollectionUtils.isEmpty(roamingScans)) {
1489                 mIsRoaming = true;
1490                 setScanResults(roamingScans);
1491             }
1492         }
1493     }
1494 
1495     /**
1496      * Attempt to update the AccessPoint with the current connection info.
1497      * This is used to set an AccessPoint to the active one if the connection info matches, or
1498      * conversely to set an AccessPoint to inactive if the connection info does not match. The RSSI
1499      * is also updated upon a match. Listeners will be notified if an update occurred.
1500      *
1501      * This is called in {@link WifiTracker#updateAccessPoints} as well as in callbacks for handling
1502      * NETWORK_STATE_CHANGED_ACTION, RSSI_CHANGED_ACTION, and onCapabilitiesChanged in WifiTracker.
1503      *
1504      * Returns true if an update occurred.
1505      */
update( @ullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo)1506     public boolean update(
1507             @Nullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
1508         boolean updated = false;
1509         final int oldLevel = getLevel();
1510         if (info != null && isInfoForThisAccessPoint(config, info)) {
1511             updated = (mInfo == null);
1512             if (!isPasspoint() && mConfig != config) {
1513                 // We do not set updated = true as we do not want to increase the amount of sorting
1514                 // and copying performed in WifiTracker at this time. If issues involving refresh
1515                 // are still seen, we will investigate further.
1516                 update(config); // Notifies the AccessPointListener of the change
1517             }
1518             if (mRssi != info.getRssi() && info.getRssi() != WifiInfo.INVALID_RSSI) {
1519                 mRssi = info.getRssi();
1520                 updated = true;
1521             } else if (mNetworkInfo != null && networkInfo != null
1522                     && mNetworkInfo.getDetailedState() != networkInfo.getDetailedState()) {
1523                 updated = true;
1524             }
1525             mInfo = info;
1526             mNetworkInfo = networkInfo;
1527         } else if (mInfo != null) {
1528             updated = true;
1529             mInfo = null;
1530             mNetworkInfo = null;
1531         }
1532         if (updated && mAccessPointListener != null) {
1533             ThreadUtils.postOnMainThread(() -> {
1534                 if (mAccessPointListener != null) {
1535                     mAccessPointListener.onAccessPointChanged(this);
1536                 }
1537             });
1538 
1539             if (oldLevel != getLevel() /* current level */) {
1540                 ThreadUtils.postOnMainThread(() -> {
1541                     if (mAccessPointListener != null) {
1542                         mAccessPointListener.onLevelChanged(this);
1543                     }
1544                 });
1545             }
1546         }
1547 
1548         return updated;
1549     }
1550 
update(@ullable WifiConfiguration config)1551     void update(@Nullable WifiConfiguration config) {
1552         mConfig = config;
1553         if (mConfig != null && !isPasspoint()) {
1554             ssid = removeDoubleQuotes(mConfig.SSID);
1555         }
1556         networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID;
1557         ThreadUtils.postOnMainThread(() -> {
1558             if (mAccessPointListener != null) {
1559                 mAccessPointListener.onAccessPointChanged(this);
1560             }
1561         });
1562     }
1563 
1564     @VisibleForTesting
setRssi(int rssi)1565     void setRssi(int rssi) {
1566         mRssi = rssi;
1567     }
1568 
1569     /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */
setUnreachable()1570     void setUnreachable() {
1571         setRssi(AccessPoint.UNREACHABLE_RSSI);
1572     }
1573 
getSpeed()1574     int getSpeed() { return mSpeed;}
1575 
1576     @Nullable
getSpeedLabel()1577     String getSpeedLabel() {
1578         return getSpeedLabel(mSpeed);
1579     }
1580 
1581     @Nullable
1582     @Speed
roundToClosestSpeedEnum(int speed)1583     private static int roundToClosestSpeedEnum(int speed) {
1584         if (speed < Speed.SLOW) {
1585             return Speed.NONE;
1586         } else if (speed < (Speed.SLOW + Speed.MODERATE) / 2) {
1587             return Speed.SLOW;
1588         } else if (speed < (Speed.MODERATE + Speed.FAST) / 2) {
1589             return Speed.MODERATE;
1590         } else if (speed < (Speed.FAST + Speed.VERY_FAST) / 2) {
1591             return Speed.FAST;
1592         } else {
1593             return Speed.VERY_FAST;
1594         }
1595     }
1596 
1597     @Nullable
getSpeedLabel(@peed int speed)1598     String getSpeedLabel(@Speed int speed) {
1599         return getSpeedLabel(mContext, speed);
1600     }
1601 
getSpeedLabel(Context context, int speed)1602     private static String getSpeedLabel(Context context, int speed) {
1603         switch (speed) {
1604             case Speed.VERY_FAST:
1605                 return context.getString(R.string.speed_label_very_fast);
1606             case Speed.FAST:
1607                 return context.getString(R.string.speed_label_fast);
1608             case Speed.MODERATE:
1609                 return context.getString(R.string.speed_label_okay);
1610             case Speed.SLOW:
1611                 return context.getString(R.string.speed_label_slow);
1612             case Speed.NONE:
1613             default:
1614                 return null;
1615         }
1616     }
1617 
1618     /** Return the speed label for a {@link ScoredNetwork} at the specified {@code rssi} level. */
1619     @Nullable
getSpeedLabel(Context context, ScoredNetwork scoredNetwork, int rssi)1620     public static String getSpeedLabel(Context context, ScoredNetwork scoredNetwork, int rssi) {
1621         return getSpeedLabel(context, roundToClosestSpeedEnum(scoredNetwork.calculateBadge(rssi)));
1622     }
1623 
1624     /** Return true if the current RSSI is reachable, and false otherwise. */
isReachable()1625     public boolean isReachable() {
1626         return mRssi != UNREACHABLE_RSSI;
1627     }
1628 
getAppLabel(String packageName, PackageManager packageManager)1629     private static CharSequence getAppLabel(String packageName, PackageManager packageManager) {
1630         CharSequence appLabel = "";
1631         ApplicationInfo appInfo = null;
1632         try {
1633             int userId = UserHandle.getUserId(UserHandle.USER_CURRENT);
1634             appInfo = packageManager.getApplicationInfoAsUser(packageName, 0 /* flags */, userId);
1635         } catch (PackageManager.NameNotFoundException e) {
1636             Log.e(TAG, "Failed to get app info", e);
1637             return appLabel;
1638         }
1639         if (appInfo != null) {
1640             appLabel = appInfo.loadLabel(packageManager);
1641         }
1642         return appLabel;
1643     }
1644 
getSummary(Context context, String ssid, DetailedState state, boolean isEphemeral, String suggestionOrSpecifierPackageName)1645     public static String getSummary(Context context, String ssid, DetailedState state,
1646             boolean isEphemeral, String suggestionOrSpecifierPackageName) {
1647         if (state == DetailedState.CONNECTED) {
1648             if (isEphemeral && !TextUtils.isEmpty(suggestionOrSpecifierPackageName)) {
1649                 CharSequence appLabel =
1650                         getAppLabel(suggestionOrSpecifierPackageName, context.getPackageManager());
1651                 return context.getString(R.string.connected_via_app, appLabel);
1652             } else if (isEphemeral) {
1653                 // Special case for connected + ephemeral networks.
1654                 final NetworkScoreManager networkScoreManager = context.getSystemService(
1655                         NetworkScoreManager.class);
1656                 NetworkScorerAppData scorer = networkScoreManager.getActiveScorer();
1657                 if (scorer != null && scorer.getRecommendationServiceLabel() != null) {
1658                     String format = context.getString(R.string.connected_via_network_scorer);
1659                     return String.format(format, scorer.getRecommendationServiceLabel());
1660                 } else {
1661                     return context.getString(R.string.connected_via_network_scorer_default);
1662                 }
1663             }
1664         }
1665 
1666         // Case when there is wifi connected without internet connectivity.
1667         final ConnectivityManager cm = (ConnectivityManager)
1668                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
1669         if (state == DetailedState.CONNECTED) {
1670             WifiManager wifiManager = context.getSystemService(WifiManager.class);
1671             NetworkCapabilities nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
1672 
1673             if (nc != null) {
1674                 if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) {
1675                     int id = context.getResources()
1676                             .getIdentifier("network_available_sign_in", "string", "android");
1677                     return context.getString(id);
1678                 } else if (nc.hasCapability(
1679                         NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
1680                     return context.getString(R.string.wifi_limited_connection);
1681                 } else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
1682                     final String mode = Settings.Global.getString(context.getContentResolver(),
1683                             Settings.Global.PRIVATE_DNS_MODE);
1684                     if (nc.isPrivateDnsBroken()) {
1685                         return context.getString(R.string.private_dns_broken);
1686                     } else {
1687                         return context.getString(R.string.wifi_connected_no_internet);
1688                     }
1689                 }
1690             }
1691         }
1692         if (state == null) {
1693             Log.w(TAG, "state is null, returning empty summary");
1694             return "";
1695         }
1696         String[] formats = context.getResources().getStringArray((ssid == null)
1697                 ? R.array.wifi_status : R.array.wifi_status_with_ssid);
1698         int index = state.ordinal();
1699 
1700         if (index >= formats.length || formats[index].length() == 0) {
1701             return "";
1702         }
1703         return String.format(formats[index], ssid);
1704     }
1705 
convertToQuotedString(String string)1706     public static String convertToQuotedString(String string) {
1707         return "\"" + string + "\"";
1708     }
1709 
getPskType(ScanResult result)1710     private static int getPskType(ScanResult result) {
1711         boolean wpa = result.capabilities.contains("WPA-PSK");
1712         boolean wpa2 = result.capabilities.contains("RSN-PSK");
1713         boolean wpa3 = result.capabilities.contains("RSN-SAE");
1714         if (wpa2 && wpa) {
1715             return PSK_WPA_WPA2;
1716         } else if (wpa2) {
1717             return PSK_WPA2;
1718         } else if (wpa) {
1719             return PSK_WPA;
1720         } else {
1721             if (!wpa3) {
1722                 // Suppress warning for WPA3 only networks
1723                 Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
1724             }
1725             return PSK_UNKNOWN;
1726         }
1727     }
1728 
getEapType(ScanResult result)1729     private static int getEapType(ScanResult result) {
1730         // WPA2-Enterprise and WPA3-Enterprise (non 192-bit) advertise RSN-EAP-CCMP
1731         if (result.capabilities.contains("RSN-EAP")) {
1732             return EAP_WPA2_WPA3;
1733         }
1734         // WPA-Enterprise advertises WPA-EAP-TKIP
1735         if (result.capabilities.contains("WPA-EAP")) {
1736             return EAP_WPA;
1737         }
1738         return EAP_UNKNOWN;
1739     }
1740 
getSecurity(Context context, ScanResult result)1741     private static int getSecurity(Context context, ScanResult result) {
1742         final boolean isWep = result.capabilities.contains("WEP");
1743         final boolean isSae = result.capabilities.contains("SAE");
1744         final boolean isPsk = result.capabilities.contains("PSK");
1745         final boolean isEapSuiteB192 = result.capabilities.contains("EAP_SUITE_B_192");
1746         final boolean isEap = result.capabilities.contains("EAP");
1747         final boolean isOwe = result.capabilities.contains("OWE");
1748         final boolean isOweTransition = result.capabilities.contains("OWE_TRANSITION");
1749 
1750         if (isSae && isPsk) {
1751             final WifiManager wifiManager = (WifiManager)
1752                     context.getSystemService(Context.WIFI_SERVICE);
1753             return wifiManager.isWpa3SaeSupported() ? SECURITY_SAE : SECURITY_PSK;
1754         }
1755         if (isOweTransition) {
1756             final WifiManager wifiManager = (WifiManager)
1757                     context.getSystemService(Context.WIFI_SERVICE);
1758             return wifiManager.isEnhancedOpenSupported() ? SECURITY_OWE : SECURITY_NONE;
1759         }
1760 
1761         if (isWep) {
1762             return SECURITY_WEP;
1763         } else if (isSae) {
1764             return SECURITY_SAE;
1765         } else if (isPsk) {
1766             return SECURITY_PSK;
1767         } else if (isEapSuiteB192) {
1768             return SECURITY_EAP_SUITE_B;
1769         } else if (isEap) {
1770             return SECURITY_EAP;
1771         } else if (isOwe) {
1772             return SECURITY_OWE;
1773         }
1774         return SECURITY_NONE;
1775     }
1776 
getSecurity(WifiConfiguration config)1777     static int getSecurity(WifiConfiguration config) {
1778         if (config.allowedKeyManagement.get(KeyMgmt.SAE)) {
1779             return SECURITY_SAE;
1780         }
1781         if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
1782             return SECURITY_PSK;
1783         }
1784         if (config.allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
1785             return SECURITY_EAP_SUITE_B;
1786         }
1787         if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
1788                 config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
1789             return SECURITY_EAP;
1790         }
1791         if (config.allowedKeyManagement.get(KeyMgmt.OWE)) {
1792             return SECURITY_OWE;
1793         }
1794         return (config.wepTxKeyIndex >= 0
1795                 && config.wepTxKeyIndex < config.wepKeys.length
1796                 && config.wepKeys[config.wepTxKeyIndex] != null)
1797                 ? SECURITY_WEP : SECURITY_NONE;
1798     }
1799 
securityToString(int security, int pskType)1800     public static String securityToString(int security, int pskType) {
1801         if (security == SECURITY_WEP) {
1802             return "WEP";
1803         } else if (security == SECURITY_PSK) {
1804             if (pskType == PSK_WPA) {
1805                 return "WPA";
1806             } else if (pskType == PSK_WPA2) {
1807                 return "WPA2";
1808             } else if (pskType == PSK_WPA_WPA2) {
1809                 return "WPA_WPA2";
1810             }
1811             return "PSK";
1812         } else if (security == SECURITY_EAP) {
1813             return "EAP";
1814         } else if (security == SECURITY_SAE) {
1815             return "SAE";
1816         } else if (security == SECURITY_EAP_SUITE_B) {
1817             return "SUITE_B";
1818         } else if (security == SECURITY_OWE) {
1819             return "OWE";
1820         }
1821         return "NONE";
1822     }
1823 
removeDoubleQuotes(String string)1824     static String removeDoubleQuotes(String string) {
1825         if (TextUtils.isEmpty(string)) {
1826             return "";
1827         }
1828         int length = string.length();
1829         if ((length > 1) && (string.charAt(0) == '"')
1830                 && (string.charAt(length - 1) == '"')) {
1831             return string.substring(1, length - 1);
1832         }
1833         return string;
1834     }
1835 
getWifiManager()1836     private WifiManager getWifiManager() {
1837         if (mWifiManager == null) {
1838             mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
1839         }
1840         return mWifiManager;
1841     }
1842 
1843     /**
1844      * Return true if this is an open network AccessPoint.
1845      */
isOpenNetwork()1846     public boolean isOpenNetwork() {
1847         return security == SECURITY_NONE || security == SECURITY_OWE;
1848     }
1849 
1850     /**
1851      * Callbacks relaying changes to the AccessPoint representation.
1852      *
1853      * <p>All methods are invoked on the Main Thread.
1854      */
1855     public interface AccessPointListener {
1856 
1857         /**
1858          * Indicates a change to the externally visible state of the AccessPoint trigger by an
1859          * update of ScanResults, saved configuration state, connection state, or score
1860          * (labels/metered) state.
1861          *
1862          * <p>Clients should refresh their view of the AccessPoint to match the updated state when
1863          * this is invoked. Overall this method is extraneous if clients are listening to
1864          * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks.
1865          *
1866          * <p>Examples of changes include signal strength, connection state, speed label, and
1867          * generally anything that would impact the summary string.
1868          *
1869          * @param accessPoint The accessPoint object the listener was registered on which has
1870          *                    changed
1871          */
onAccessPointChanged(AccessPoint accessPoint)1872         @MainThread void onAccessPointChanged(AccessPoint accessPoint);
1873         /**
1874          * Indicates the "wifi pie signal level" has changed, retrieved via calls to
1875          * {@link AccessPoint#getLevel()}.
1876          *
1877          * <p>This call is a subset of {@link #onAccessPointChanged(AccessPoint)} , hence is also
1878          * extraneous if the client is already reacting to that or the
1879          * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks.
1880          *
1881          * @param accessPoint The accessPoint object the listener was registered on whose level has
1882          *                    changed
1883          */
onLevelChanged(AccessPoint accessPoint)1884         @MainThread void onLevelChanged(AccessPoint accessPoint);
1885     }
1886 
isVerboseLoggingEnabled()1887     private static boolean isVerboseLoggingEnabled() {
1888         return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
1889     }
1890 
1891     /**
1892      * Callbacks relaying changes to the OSU provisioning status started in startOsuProvisioning().
1893      *
1894      * All methods are invoked on the Main Thread
1895      */
1896     @VisibleForTesting
1897     class AccessPointProvisioningCallback extends ProvisioningCallback {
1898         @Override
onProvisioningFailure(int status)1899         @MainThread public void onProvisioningFailure(int status) {
1900             if (TextUtils.equals(mOsuStatus, mContext.getString(R.string.osu_completing_sign_up))) {
1901                 mOsuFailure = mContext.getString(R.string.osu_sign_up_failed);
1902             } else {
1903                 mOsuFailure = mContext.getString(R.string.osu_connect_failed);
1904             }
1905             mOsuStatus = null;
1906             mOsuProvisioningComplete = false;
1907             ThreadUtils.postOnMainThread(() -> {
1908                 if (mAccessPointListener != null) {
1909                     mAccessPointListener.onAccessPointChanged(AccessPoint.this);
1910                 }
1911             });
1912         }
1913 
1914         @Override
onProvisioningStatus(int status)1915         @MainThread public void onProvisioningStatus(int status) {
1916             String newStatus = null;
1917             switch (status) {
1918                 case OSU_STATUS_AP_CONNECTING:
1919                 case OSU_STATUS_AP_CONNECTED:
1920                 case OSU_STATUS_SERVER_CONNECTING:
1921                 case OSU_STATUS_SERVER_VALIDATED:
1922                 case OSU_STATUS_SERVER_CONNECTED:
1923                 case OSU_STATUS_INIT_SOAP_EXCHANGE:
1924                 case OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE:
1925                     newStatus = String.format(mContext.getString(R.string.osu_opening_provider),
1926                             mOsuProvider.getFriendlyName());
1927                     break;
1928                 case OSU_STATUS_REDIRECT_RESPONSE_RECEIVED:
1929                 case OSU_STATUS_SECOND_SOAP_EXCHANGE:
1930                 case OSU_STATUS_THIRD_SOAP_EXCHANGE:
1931                 case OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS:
1932                     newStatus = mContext.getString(
1933                             R.string.osu_completing_sign_up);
1934                     break;
1935             }
1936             boolean updated = !TextUtils.equals(mOsuStatus, newStatus);
1937             mOsuStatus = newStatus;
1938             mOsuFailure = null;
1939             mOsuProvisioningComplete = false;
1940             if (updated) {
1941                 ThreadUtils.postOnMainThread(() -> {
1942                     if (mAccessPointListener != null) {
1943                         mAccessPointListener.onAccessPointChanged(AccessPoint.this);
1944                     }
1945                 });
1946             }
1947         }
1948 
1949         @Override
onProvisioningComplete()1950         @MainThread public void onProvisioningComplete() {
1951             mOsuProvisioningComplete = true;
1952             mOsuFailure = null;
1953             mOsuStatus = null;
1954 
1955             ThreadUtils.postOnMainThread(() -> {
1956                 if (mAccessPointListener != null) {
1957                     mAccessPointListener.onAccessPointChanged(AccessPoint.this);
1958                 }
1959             });
1960 
1961             // Connect to the freshly provisioned network.
1962             WifiManager wifiManager = getWifiManager();
1963 
1964             PasspointConfiguration passpointConfig = wifiManager
1965                     .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider))
1966                     .get(mOsuProvider);
1967             if (passpointConfig == null) {
1968                 Log.e(TAG, "Missing PasspointConfiguration for newly provisioned network!");
1969                 if (mConnectListener != null) {
1970                     mConnectListener.onFailure(0);
1971                 }
1972                 return;
1973             }
1974 
1975             String uniqueId = passpointConfig.getUniqueId();
1976             for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing :
1977                     wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) {
1978                 WifiConfiguration config = pairing.first;
1979                 if (TextUtils.equals(config.getKey(), uniqueId)) {
1980                     List<ScanResult> homeScans =
1981                             pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
1982                     List<ScanResult> roamingScans =
1983                             pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
1984 
1985                     AccessPoint connectionAp =
1986                             new AccessPoint(mContext, config, homeScans, roamingScans);
1987                     wifiManager.connect(connectionAp.getConfig(), mConnectListener);
1988                     return;
1989                 }
1990             }
1991             if (mConnectListener != null) {
1992                 mConnectListener.onFailure(0);
1993             }
1994         }
1995     }
1996 
isPskSaeTransitionMode()1997     public boolean isPskSaeTransitionMode() {
1998         return mIsPskSaeTransitionMode;
1999     }
2000 
isOweTransitionMode()2001     public boolean isOweTransitionMode() {
2002         return mIsOweTransitionMode;
2003     }
2004 
isPskSaeTransitionMode(ScanResult scanResult)2005     private static boolean isPskSaeTransitionMode(ScanResult scanResult) {
2006         return scanResult.capabilities.contains("PSK")
2007                 && scanResult.capabilities.contains("SAE");
2008     }
2009 
isOweTransitionMode(ScanResult scanResult)2010     private static boolean isOweTransitionMode(ScanResult scanResult) {
2011         return scanResult.capabilities.contains("OWE_TRANSITION");
2012     }
2013 
isSameSsidOrBssid(ScanResult scanResult)2014     private boolean isSameSsidOrBssid(ScanResult scanResult) {
2015         if (scanResult == null) {
2016             return false;
2017         }
2018 
2019         if (TextUtils.equals(ssid, scanResult.SSID)) {
2020             return true;
2021         } else if (scanResult.BSSID != null && TextUtils.equals(bssid, scanResult.BSSID)) {
2022             return true;
2023         }
2024         return false;
2025     }
2026 
isSameSsidOrBssid(WifiInfo wifiInfo)2027     private boolean isSameSsidOrBssid(WifiInfo wifiInfo) {
2028         if (wifiInfo == null) {
2029             return false;
2030         }
2031 
2032         if (TextUtils.equals(ssid, removeDoubleQuotes(wifiInfo.getSSID()))) {
2033             return true;
2034         } else if (wifiInfo.getBSSID() != null && TextUtils.equals(bssid, wifiInfo.getBSSID())) {
2035             return true;
2036         }
2037         return false;
2038     }
2039 
isSameSsidOrBssid(AccessPoint accessPoint)2040     private boolean isSameSsidOrBssid(AccessPoint accessPoint) {
2041         if (accessPoint == null) {
2042             return false;
2043         }
2044 
2045         if (TextUtils.equals(ssid, accessPoint.getSsid())) {
2046             return true;
2047         } else if (accessPoint.getBssid() != null
2048                 && TextUtils.equals(bssid, accessPoint.getBssid())) {
2049             return true;
2050         }
2051         return false;
2052     }
2053 }
2054