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