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