Home | History | Annotate | Download | only in wifi
      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 
    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      */
    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      */
    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      */
    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      */
    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 
    378     AccessPoint(Context context, Collection<ScanResult> results) {
    379         mContext = context;
    380         setScanResults(results);
    381         updateKey();
    382     }
    383 
    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. */
    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
    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
    458     public boolean equals(Object other) {
    459         if (!(other instanceof AccessPoint)) return false;
    460         return (this.compareTo((AccessPoint) other) == 0);
    461     }
    462 
    463     @Override
    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
    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      */
    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      */
    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      */
    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. */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    701     public String getKey() {
    702         return mKey;
    703     }
    704 
    705     /**
    706      * Determines if the other AccessPoint represents the same network as this AccessPoint
    707      */
    708     public boolean matches(AccessPoint other) {
    709         return getKey().equals(other.getKey());
    710     }
    711 
    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 
    737     public WifiConfiguration getConfig() {
    738         return mConfig;
    739     }
    740 
    741     public String getPasspointFqdn() {
    742         return mFqdn;
    743     }
    744 
    745     public void clearConfig() {
    746         mConfig = null;
    747         networkId = WifiConfiguration.INVALID_NETWORK_ID;
    748     }
    749 
    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      */
    760     public int getLevel() {
    761         return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
    762     }
    763 
    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      */
    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 
    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      */
    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      */
    842     public boolean isMetered() {
    843         return mIsScoredNetworkMetered
    844                 || WifiConfiguration.isMetered(mConfig, mInfo);
    845     }
    846 
    847     public NetworkInfo getNetworkInfo() {
    848         return mNetworkInfo;
    849     }
    850 
    851     public int getSecurity() {
    852         return security;
    853     }
    854 
    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 
    923     public String getSsidStr() {
    924         return ssid;
    925     }
    926 
    927     public String getBssid() {
    928         return bssid;
    929     }
    930 
    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
    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 
    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 
    958     public boolean isCarrierAp() {
    959         return mIsCarrierAp;
    960     }
    961 
    962     public int getCarrierApEapType() {
    963         return mCarrierApEapType;
    964     }
    965 
    966     public String getCarrierName() {
    967         return mCarrierName;
    968     }
    969 
    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      */
    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 
   1011     public String getSummary() {
   1012         return getSettingsSummary();
   1013     }
   1014 
   1015     public String getSettingsSummary() {
   1016         return getSettingsSummary(false /*convertSavedAsDisconnected*/);
   1017     }
   1018 
   1019     /**
   1020      * Returns the summary for the AccessPoint.
   1021      */
   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      */
   1132     public boolean isActive() {
   1133         return mNetworkInfo != null &&
   1134                 (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
   1135                  mNetworkInfo.getState() != State.DISCONNECTED);
   1136     }
   1137 
   1138     public boolean isConnectable() {
   1139         return getLevel() != -1 && getDetailedState() == null;
   1140     }
   1141 
   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      */
   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      */
   1157     public boolean isPasspointConfig() {
   1158         return mFqdn != null && mConfig == null;
   1159     }
   1160 
   1161     /**
   1162      * Return true if this AccessPoint represents an OSU Provider.
   1163      */
   1164     public boolean isOsuProvider() {
   1165         return mOsuProvider != null;
   1166     }
   1167 
   1168     /**
   1169      * Starts the OSU Provisioning flow.
   1170      */
   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      */
   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 
   1206     public boolean isSaved() {
   1207         return mConfig != null;
   1208     }
   1209 
   1210     public Object getTag() {
   1211         return mTag;
   1212     }
   1213 
   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      */
   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 
   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 
   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 
   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      */
   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      */
   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      */
   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 
   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
   1450     void setRssi(int rssi) {
   1451         mRssi = rssi;
   1452     }
   1453 
   1454     /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */
   1455     void setUnreachable() {
   1456         setRssi(AccessPoint.UNREACHABLE_RSSI);
   1457     }
   1458 
   1459     int getSpeed() { return mSpeed;}
   1460 
   1461     @Nullable
   1462     String getSpeedLabel() {
   1463         return getSpeedLabel(mSpeed);
   1464     }
   1465 
   1466     @Nullable
   1467     @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
   1483     String getSpeedLabel(@Speed int speed) {
   1484         return getSpeedLabel(mContext, speed);
   1485     }
   1486 
   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
   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. */
   1510     public boolean isReachable() {
   1511         return mRssi != UNREACHABLE_RSSI;
   1512     }
   1513 
   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 
   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 
   1590     public static String convertToQuotedString(String string) {
   1591         return "\"" + string + "\"";
   1592     }
   1593 
   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 
   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 
   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 
   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 
   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 
   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 
   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          */
   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          */
   1750         @MainThread void onLevelChanged(AccessPoint accessPoint);
   1751     }
   1752 
   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
   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
   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
   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