• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.wifitrackerlib;
18 
19 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS;
20 import static android.net.wifi.WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2;
21 import static android.net.wifi.WifiInfo.SECURITY_TYPE_PASSPOINT_R3;
22 import static android.net.wifi.WifiInfo.SECURITY_TYPE_UNKNOWN;
23 import static android.net.wifi.WifiInfo.sanitizeSsid;
24 
25 import static androidx.core.util.Preconditions.checkNotNull;
26 
27 import static com.android.wifitrackerlib.Utils.getAutoConnectDescription;
28 import static com.android.wifitrackerlib.Utils.getAverageSpeedFromScanResults;
29 import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel;
30 import static com.android.wifitrackerlib.Utils.getConnectedDescription;
31 import static com.android.wifitrackerlib.Utils.getConnectingDescription;
32 import static com.android.wifitrackerlib.Utils.getDisconnectedDescription;
33 import static com.android.wifitrackerlib.Utils.getImsiProtectionDescription;
34 import static com.android.wifitrackerlib.Utils.getMeteredDescription;
35 import static com.android.wifitrackerlib.Utils.getSpeedDescription;
36 import static com.android.wifitrackerlib.Utils.getSpeedFromWifiInfo;
37 import static com.android.wifitrackerlib.Utils.getVerboseLoggingDescription;
38 
39 import android.content.Context;
40 import android.net.ConnectivityManager;
41 import android.net.NetworkCapabilities;
42 import android.net.NetworkInfo;
43 import android.net.wifi.ScanResult;
44 import android.net.wifi.WifiConfiguration;
45 import android.net.wifi.WifiInfo;
46 import android.net.wifi.WifiManager;
47 import android.net.wifi.WifiNetworkScoreCache;
48 import android.net.wifi.hotspot2.PasspointConfiguration;
49 import android.os.Handler;
50 import android.text.TextUtils;
51 import android.util.Log;
52 
53 import androidx.annotation.NonNull;
54 import androidx.annotation.Nullable;
55 import androidx.annotation.WorkerThread;
56 
57 import com.android.internal.annotations.VisibleForTesting;
58 
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.List;
62 import java.util.StringJoiner;
63 
64 /**
65  * WifiEntry representation of a subscribed Passpoint network, uniquely identified by FQDN.
66  */
67 @VisibleForTesting
68 public class PasspointWifiEntry extends WifiEntry implements WifiEntry.WifiEntryCallback {
69     static final String TAG = "PasspointWifiEntry";
70     public static final String KEY_PREFIX = "PasspointWifiEntry:";
71 
72     private final List<ScanResult> mCurrentHomeScanResults = new ArrayList<>();
73     private final List<ScanResult> mCurrentRoamingScanResults = new ArrayList<>();
74 
75     @NonNull private final String mKey;
76     @NonNull private final String mFqdn;
77     @NonNull private final String mFriendlyName;
78     @NonNull private final Context mContext;
79     @Nullable
80     private PasspointConfiguration mPasspointConfig;
81     @Nullable private WifiConfiguration mWifiConfig;
82     private List<Integer> mTargetSecurityTypes =
83             List.of(SECURITY_TYPE_PASSPOINT_R1_R2, SECURITY_TYPE_PASSPOINT_R3);
84 
85     private boolean mIsRoaming = false;
86     private OsuWifiEntry mOsuWifiEntry;
87     private boolean mShouldAutoOpenCaptivePortal = false;
88 
89     protected long mSubscriptionExpirationTimeInMillis;
90 
91     // PasspointConfiguration#setMeteredOverride(int meteredOverride) is a hide API and we can't
92     // set it in PasspointWifiEntry#setMeteredChoice(int meteredChoice).
93     // For PasspointWifiEntry#getMeteredChoice() to return correct value right after
94     // PasspointWifiEntry#setMeteredChoice(int meteredChoice), cache
95     // PasspointConfiguration#getMeteredOverride() in this variable.
96     private int mMeteredOverride = METERED_CHOICE_AUTO;
97 
98     /**
99      * Create a PasspointWifiEntry with the associated PasspointConfiguration
100      */
PasspointWifiEntry(@onNull Context context, @NonNull Handler callbackHandler, @NonNull PasspointConfiguration passpointConfig, @NonNull WifiManager wifiManager, @NonNull WifiNetworkScoreCache scoreCache, boolean forSavedNetworksPage)101     PasspointWifiEntry(@NonNull Context context, @NonNull Handler callbackHandler,
102             @NonNull PasspointConfiguration passpointConfig,
103             @NonNull WifiManager wifiManager,
104             @NonNull WifiNetworkScoreCache scoreCache,
105             boolean forSavedNetworksPage) throws IllegalArgumentException {
106         super(callbackHandler, wifiManager, scoreCache, forSavedNetworksPage);
107 
108         checkNotNull(passpointConfig, "Cannot construct with null PasspointConfiguration!");
109 
110         mContext = context;
111         mPasspointConfig = passpointConfig;
112         mKey = uniqueIdToPasspointWifiEntryKey(passpointConfig.getUniqueId());
113         mFqdn = passpointConfig.getHomeSp().getFqdn();
114         checkNotNull(mFqdn, "Cannot construct with null PasspointConfiguration FQDN!");
115         mFriendlyName = passpointConfig.getHomeSp().getFriendlyName();
116         mSubscriptionExpirationTimeInMillis =
117                 passpointConfig.getSubscriptionExpirationTimeMillis();
118         mMeteredOverride = mPasspointConfig.getMeteredOverride();
119     }
120 
121     /**
122      * Create a PasspointWifiEntry with the associated WifiConfiguration for use with network
123      * suggestions, since WifiManager#getAllMatchingWifiConfigs() does not provide a corresponding
124      * PasspointConfiguration.
125      */
PasspointWifiEntry(@onNull Context context, @NonNull Handler callbackHandler, @NonNull WifiConfiguration wifiConfig, @NonNull WifiManager wifiManager, @NonNull WifiNetworkScoreCache scoreCache, boolean forSavedNetworksPage)126     PasspointWifiEntry(@NonNull Context context, @NonNull Handler callbackHandler,
127             @NonNull WifiConfiguration wifiConfig,
128             @NonNull WifiManager wifiManager,
129             @NonNull WifiNetworkScoreCache scoreCache,
130             boolean forSavedNetworksPage) throws IllegalArgumentException {
131         super(callbackHandler, wifiManager, scoreCache, forSavedNetworksPage);
132 
133         checkNotNull(wifiConfig, "Cannot construct with null WifiConfiguration!");
134         if (!wifiConfig.isPasspoint()) {
135             throw new IllegalArgumentException("Given WifiConfiguration is not for Passpoint!");
136         }
137 
138         mContext = context;
139         mWifiConfig = wifiConfig;
140         mKey = uniqueIdToPasspointWifiEntryKey(wifiConfig.getKey());
141         mFqdn = wifiConfig.FQDN;
142         checkNotNull(mFqdn, "Cannot construct with null WifiConfiguration FQDN!");
143         mFriendlyName = mWifiConfig.providerFriendlyName;
144     }
145 
146     @Override
getKey()147     public String getKey() {
148         return mKey;
149     }
150 
151     @Override
152     @ConnectedState
getConnectedState()153     public synchronized int getConnectedState() {
154         if (isExpired()) {
155             if (super.getConnectedState() == CONNECTED_STATE_DISCONNECTED
156                     && mOsuWifiEntry != null) {
157                 return mOsuWifiEntry.getConnectedState();
158             }
159         }
160         return super.getConnectedState();
161     }
162 
163     @Override
getTitle()164     public String getTitle() {
165         return mFriendlyName;
166     }
167 
168     @Override
getSummary(boolean concise)169     public synchronized String getSummary(boolean concise) {
170         StringJoiner sj = new StringJoiner(mContext.getString(
171                 R.string.wifitrackerlib_summary_separator));
172 
173         if (isExpired()) {
174             if (mOsuWifiEntry != null) {
175                 sj.add(mOsuWifiEntry.getSummary(concise));
176             } else {
177                 sj.add(mContext.getString(R.string.wifitrackerlib_wifi_passpoint_expired));
178             }
179         } else {
180             final String connectedStateDescription;
181             final @ConnectedState int connectedState = getConnectedState();
182             switch (connectedState) {
183                 case CONNECTED_STATE_DISCONNECTED:
184                     connectedStateDescription = getDisconnectedDescription(mContext,
185                             mWifiConfig,
186                             mForSavedNetworksPage,
187                             concise);
188                     break;
189                 case CONNECTED_STATE_CONNECTING:
190                     connectedStateDescription = getConnectingDescription(mContext, mNetworkInfo);
191                     break;
192                 case CONNECTED_STATE_CONNECTED:
193                     connectedStateDescription = getConnectedDescription(mContext,
194                             mWifiConfig,
195                             mNetworkCapabilities,
196                             null /* recommendationServiceLabel */,
197                             mIsDefaultNetwork,
198                             mIsLowQuality);
199                     break;
200                 default:
201                     Log.e(TAG, "getConnectedState() returned unknown state: " + connectedState);
202                     connectedStateDescription = null;
203             }
204             if (!TextUtils.isEmpty(connectedStateDescription)) {
205                 sj.add(connectedStateDescription);
206             }
207         }
208 
209         String speedDescription = getSpeedDescription(mContext, this);
210         if (!TextUtils.isEmpty(speedDescription)) {
211             sj.add(speedDescription);
212         }
213 
214         String autoConnectDescription = getAutoConnectDescription(mContext, this);
215         if (!TextUtils.isEmpty(autoConnectDescription)) {
216             sj.add(autoConnectDescription);
217         }
218 
219         String meteredDescription = getMeteredDescription(mContext, this);
220         if (!TextUtils.isEmpty(meteredDescription)) {
221             sj.add(meteredDescription);
222         }
223 
224         if (!concise) {
225             String verboseLoggingDescription = getVerboseLoggingDescription(this);
226             if (!TextUtils.isEmpty(verboseLoggingDescription)) {
227                 sj.add(verboseLoggingDescription);
228             }
229         }
230 
231         return sj.toString();
232     }
233 
234     @Override
getSecondSummary()235     public synchronized CharSequence getSecondSummary() {
236         return getConnectedState() == CONNECTED_STATE_CONNECTED
237                 ? getImsiProtectionDescription(mContext, mWifiConfig) : "";
238     }
239 
240     @Override
getSsid()241     public synchronized String getSsid() {
242         if (mWifiInfo != null) {
243             return sanitizeSsid(mWifiInfo.getSSID());
244         }
245 
246         return mWifiConfig != null ? sanitizeSsid(mWifiConfig.SSID) : null;
247     }
248 
249     @Override
getSecurityTypes()250     public synchronized List<Integer> getSecurityTypes() {
251         return new ArrayList<>(mTargetSecurityTypes);
252     }
253 
254     @Override
getMacAddress()255     public synchronized String getMacAddress() {
256         if (mWifiInfo != null) {
257             final String wifiInfoMac = mWifiInfo.getMacAddress();
258             if (!TextUtils.isEmpty(wifiInfoMac)
259                     && !TextUtils.equals(wifiInfoMac, DEFAULT_MAC_ADDRESS)) {
260                 return wifiInfoMac;
261             }
262         }
263         if (mWifiConfig == null || getPrivacy() != PRIVACY_RANDOMIZED_MAC) {
264             final String[] factoryMacs = mWifiManager.getFactoryMacAddresses();
265             if (factoryMacs.length > 0) {
266                 return factoryMacs[0];
267             }
268             return null;
269         }
270         return mWifiConfig.getRandomizedMacAddress().toString();
271     }
272 
273     @Override
isMetered()274     public synchronized boolean isMetered() {
275         return getMeteredChoice() == METERED_CHOICE_METERED
276                 || (mWifiConfig != null && mWifiConfig.meteredHint);
277     }
278 
279     @Override
isSuggestion()280     public synchronized boolean isSuggestion() {
281         return mWifiConfig != null && mWifiConfig.fromWifiNetworkSuggestion;
282     }
283 
284     @Override
isSubscription()285     public synchronized boolean isSubscription() {
286         return mPasspointConfig != null;
287     }
288 
289     @Override
canConnect()290     public synchronized boolean canConnect() {
291         if (isExpired()) {
292             return mOsuWifiEntry != null && mOsuWifiEntry.canConnect();
293         }
294 
295         return mLevel != WIFI_LEVEL_UNREACHABLE
296                 && getConnectedState() == CONNECTED_STATE_DISCONNECTED && mWifiConfig != null;
297     }
298 
299     @Override
connect(@ullable ConnectCallback callback)300     public synchronized void connect(@Nullable ConnectCallback callback) {
301         if (isExpired()) {
302             if (mOsuWifiEntry != null) {
303                 mOsuWifiEntry.connect(callback);
304                 return;
305             }
306         }
307         // We should flag this network to auto-open captive portal since this method represents
308         // the user manually connecting to a network (i.e. not auto-join).
309         mShouldAutoOpenCaptivePortal = true;
310         mConnectCallback = callback;
311 
312         if (mWifiConfig == null) {
313             // We should not be able to call connect() if mWifiConfig is null
314             new ConnectActionListener().onFailure(0);
315         }
316         mWifiManager.stopRestrictingAutoJoinToSubscriptionId();
317         mWifiManager.connect(mWifiConfig, new ConnectActionListener());
318     }
319 
320     @Override
canDisconnect()321     public boolean canDisconnect() {
322         return getConnectedState() == CONNECTED_STATE_CONNECTED;
323     }
324 
325     @Override
disconnect(@ullable DisconnectCallback callback)326     public synchronized void disconnect(@Nullable DisconnectCallback callback) {
327         if (canDisconnect()) {
328             mCalledDisconnect = true;
329             mDisconnectCallback = callback;
330             mCallbackHandler.postDelayed(() -> {
331                 if (callback != null && mCalledDisconnect) {
332                     callback.onDisconnectResult(
333                             DisconnectCallback.DISCONNECT_STATUS_FAILURE_UNKNOWN);
334                 }
335             }, 10_000 /* delayMillis */);
336             mWifiManager.disableEphemeralNetwork(mFqdn);
337             mWifiManager.disconnect();
338         }
339     }
340 
341     @Override
canForget()342     public synchronized boolean canForget() {
343         return !isSuggestion() && mPasspointConfig != null;
344     }
345 
346     @Override
forget(@ullable ForgetCallback callback)347     public synchronized void forget(@Nullable ForgetCallback callback) {
348         if (!canForget()) {
349             return;
350         }
351 
352         mForgetCallback = callback;
353         mWifiManager.removePasspointConfiguration(mPasspointConfig.getHomeSp().getFqdn());
354         new ForgetActionListener().onSuccess();
355     }
356 
357     @Override
358     @MeteredChoice
getMeteredChoice()359     public synchronized int getMeteredChoice() {
360         if (mMeteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) {
361             return METERED_CHOICE_METERED;
362         } else if (mMeteredOverride == WifiConfiguration.METERED_OVERRIDE_NOT_METERED) {
363             return METERED_CHOICE_UNMETERED;
364         }
365         return METERED_CHOICE_AUTO;
366     }
367 
368     @Override
canSetMeteredChoice()369     public synchronized boolean canSetMeteredChoice() {
370         return !isSuggestion() && mPasspointConfig != null;
371     }
372 
373     @Override
setMeteredChoice(int meteredChoice)374     public synchronized void setMeteredChoice(int meteredChoice) {
375         if (mPasspointConfig == null || !canSetMeteredChoice()) {
376             return;
377         }
378 
379         switch (meteredChoice) {
380             case METERED_CHOICE_AUTO:
381                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE;
382                 break;
383             case METERED_CHOICE_METERED:
384                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
385                 break;
386             case METERED_CHOICE_UNMETERED:
387                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
388                 break;
389             default:
390                 // Do nothing.
391                 return;
392         }
393         mWifiManager.setPasspointMeteredOverride(mPasspointConfig.getHomeSp().getFqdn(),
394                 mMeteredOverride);
395     }
396 
397     @Override
canSetPrivacy()398     public synchronized boolean canSetPrivacy() {
399         return !isSuggestion() && mPasspointConfig != null;
400     }
401 
402     @Override
403     @Privacy
getPrivacy()404     public synchronized int getPrivacy() {
405         if (mPasspointConfig == null) {
406             return PRIVACY_RANDOMIZED_MAC;
407         }
408 
409         return mPasspointConfig.isMacRandomizationEnabled()
410                 ? PRIVACY_RANDOMIZED_MAC : PRIVACY_DEVICE_MAC;
411     }
412 
413     @Override
setPrivacy(int privacy)414     public synchronized void setPrivacy(int privacy) {
415         if (mPasspointConfig == null || !canSetPrivacy()) {
416             return;
417         }
418 
419         mWifiManager.setMacRandomizationSettingPasspointEnabled(
420                 mPasspointConfig.getHomeSp().getFqdn(),
421                 privacy == PRIVACY_DEVICE_MAC ? false : true);
422     }
423 
424     @Override
isAutoJoinEnabled()425     public synchronized boolean isAutoJoinEnabled() {
426         // Suggestion network; use WifiConfig instead
427         if (mPasspointConfig != null) {
428             return mPasspointConfig.isAutojoinEnabled();
429         }
430         if (mWifiConfig != null) {
431             return mWifiConfig.allowAutojoin;
432         }
433         return false;
434     }
435 
436     @Override
canSetAutoJoinEnabled()437     public synchronized boolean canSetAutoJoinEnabled() {
438         return mPasspointConfig != null || mWifiConfig != null;
439     }
440 
441     @Override
setAutoJoinEnabled(boolean enabled)442     public synchronized void setAutoJoinEnabled(boolean enabled) {
443         if (mPasspointConfig != null) {
444             mWifiManager.allowAutojoinPasspoint(mPasspointConfig.getHomeSp().getFqdn(), enabled);
445         } else if (mWifiConfig != null) {
446             mWifiManager.allowAutojoin(mWifiConfig.networkId, enabled);
447         }
448     }
449 
450     @Override
getSecurityString(boolean concise)451     public String getSecurityString(boolean concise) {
452         return mContext.getString(R.string.wifitrackerlib_wifi_security_passpoint);
453     }
454 
455     @Override
isExpired()456     public synchronized boolean isExpired() {
457         if (mSubscriptionExpirationTimeInMillis <= 0) {
458             // Expiration time not specified.
459             return false;
460         } else {
461             return System.currentTimeMillis() >= mSubscriptionExpirationTimeInMillis;
462         }
463     }
464 
465     @WorkerThread
updatePasspointConfig(@ullable PasspointConfiguration passpointConfig)466     synchronized void updatePasspointConfig(@Nullable PasspointConfiguration passpointConfig) {
467         mPasspointConfig = passpointConfig;
468         if (mPasspointConfig != null) {
469             mSubscriptionExpirationTimeInMillis =
470                     passpointConfig.getSubscriptionExpirationTimeMillis();
471             mMeteredOverride = passpointConfig.getMeteredOverride();
472         }
473         notifyOnUpdated();
474     }
475 
476     @WorkerThread
updateScanResultInfo(@ullable WifiConfiguration wifiConfig, @Nullable List<ScanResult> homeScanResults, @Nullable List<ScanResult> roamingScanResults)477     synchronized void updateScanResultInfo(@Nullable WifiConfiguration wifiConfig,
478             @Nullable List<ScanResult> homeScanResults,
479             @Nullable List<ScanResult> roamingScanResults)
480             throws IllegalArgumentException {
481         mIsRoaming = false;
482         mWifiConfig = wifiConfig;
483         mCurrentHomeScanResults.clear();
484         mCurrentRoamingScanResults.clear();
485         if (homeScanResults != null) {
486             mCurrentHomeScanResults.addAll(homeScanResults);
487         }
488         if (roamingScanResults != null) {
489             mCurrentRoamingScanResults.addAll(roamingScanResults);
490         }
491         if (mWifiConfig != null) {
492             List<ScanResult> currentScanResults = new ArrayList<>();
493             ScanResult bestScanResult = null;
494             if (homeScanResults != null && !homeScanResults.isEmpty()) {
495                 currentScanResults.addAll(homeScanResults);
496             } else if (roamingScanResults != null && !roamingScanResults.isEmpty()) {
497                 currentScanResults.addAll(roamingScanResults);
498                 mIsRoaming = true;
499             }
500             bestScanResult = getBestScanResultByLevel(currentScanResults);
501             if (bestScanResult != null) {
502                 mWifiConfig.SSID = "\"" + bestScanResult.SSID + "\"";
503             }
504             if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) {
505                 mLevel = bestScanResult != null
506                         ? mWifiManager.calculateSignalLevel(bestScanResult.level)
507                         : WIFI_LEVEL_UNREACHABLE;
508                 // Average speed is used to prevent speed label flickering from multiple APs.
509                 mSpeed = getAverageSpeedFromScanResults(mScoreCache, currentScanResults);
510             }
511         } else {
512             mLevel = WIFI_LEVEL_UNREACHABLE;
513         }
514         notifyOnUpdated();
515     }
516 
517     @Override
updateSecurityTypes()518     protected synchronized void updateSecurityTypes() {
519         if (mWifiInfo != null) {
520             final int wifiInfoSecurity = mWifiInfo.getCurrentSecurityType();
521             if (wifiInfoSecurity != SECURITY_TYPE_UNKNOWN) {
522                 mTargetSecurityTypes = Collections.singletonList(wifiInfoSecurity);
523                 return;
524             }
525         }
526     }
527 
528     @WorkerThread
onScoreCacheUpdated()529     synchronized void onScoreCacheUpdated() {
530         if (mWifiInfo != null) {
531             mSpeed = getSpeedFromWifiInfo(mScoreCache, mWifiInfo);
532         } else {
533             // Average speed is used to prevent speed label flickering from multiple APs.
534             if (!mCurrentHomeScanResults.isEmpty()) {
535                 mSpeed = getAverageSpeedFromScanResults(mScoreCache, mCurrentHomeScanResults);
536             } else {
537                 mSpeed = getAverageSpeedFromScanResults(mScoreCache,
538                         mCurrentRoamingScanResults);
539             }
540         }
541         notifyOnUpdated();
542     }
543 
544     @WorkerThread
545     @Override
connectionInfoMatches(@onNull WifiInfo wifiInfo, @NonNull NetworkInfo networkInfo)546     protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo,
547             @NonNull NetworkInfo networkInfo) {
548         if (!wifiInfo.isPasspointAp()) {
549             return false;
550         }
551 
552         // Match with FQDN until WifiInfo supports returning the passpoint uniqueID
553         return TextUtils.equals(wifiInfo.getPasspointFqdn(), mFqdn);
554     }
555 
556     @WorkerThread
557     @Override
updateNetworkCapabilities(@ullable NetworkCapabilities capabilities)558     synchronized void updateNetworkCapabilities(@Nullable NetworkCapabilities capabilities) {
559         super.updateNetworkCapabilities(capabilities);
560 
561         // Auto-open an available captive portal if the user manually connected to this network.
562         if (canSignIn() && mShouldAutoOpenCaptivePortal) {
563             mShouldAutoOpenCaptivePortal = false;
564             signIn(null /* callback */);
565         }
566     }
567 
568     @NonNull
uniqueIdToPasspointWifiEntryKey(@onNull String uniqueId)569     static String uniqueIdToPasspointWifiEntryKey(@NonNull String uniqueId) {
570         checkNotNull(uniqueId, "Cannot create key with null unique id!");
571         return KEY_PREFIX + uniqueId;
572     }
573 
574     @Override
getScanResultDescription()575     protected String getScanResultDescription() {
576         // TODO(b/70983952): Fill this method in.
577         return "";
578     }
579 
580     @Override
getNetworkSelectionDescription()581     synchronized String getNetworkSelectionDescription() {
582         return Utils.getNetworkSelectionDescription(mWifiConfig);
583     }
584 
585     /** Pass a reference to a matching OsuWifiEntry for expiration handling */
setOsuWifiEntry(OsuWifiEntry osuWifiEntry)586     synchronized void setOsuWifiEntry(OsuWifiEntry osuWifiEntry) {
587         mOsuWifiEntry = osuWifiEntry;
588         if (mOsuWifiEntry != null) {
589             mOsuWifiEntry.setListener(this);
590         }
591     }
592 
593     /** Callback for updates to the linked OsuWifiEntry */
594     @Override
onUpdated()595     public void onUpdated() {
596         notifyOnUpdated();
597     }
598 
599     @Override
canSignIn()600     public synchronized boolean canSignIn() {
601         return mNetworkCapabilities != null
602                 && mNetworkCapabilities.hasCapability(
603                 NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
604     }
605 
606     @Override
signIn(@ullable SignInCallback callback)607     public void signIn(@Nullable SignInCallback callback) {
608         if (canSignIn()) {
609             // canSignIn() implies that this WifiEntry is the currently connected network, so use
610             // getCurrentNetwork() to start the captive portal app.
611             ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
612                     .startCaptivePortalApp(mWifiManager.getCurrentNetwork());
613         }
614     }
615 
616     /** Get the PasspointConfiguration instance of the entry. */
getPasspointConfig()617     public PasspointConfiguration getPasspointConfig() {
618         return mPasspointConfig;
619     }
620 }
621