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