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