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