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 isDefaultNetwork(), 199 isLowQuality(), 200 mConnectivityReport); 201 break; 202 default: 203 Log.e(TAG, "getConnectedState() returned unknown state: " + connectedState); 204 connectedStateDescription = null; 205 } 206 if (!TextUtils.isEmpty(connectedStateDescription)) { 207 sj.add(connectedStateDescription); 208 } 209 } 210 211 String autoConnectDescription = getAutoConnectDescription(mContext, this); 212 if (!TextUtils.isEmpty(autoConnectDescription)) { 213 sj.add(autoConnectDescription); 214 } 215 216 String meteredDescription = getMeteredDescription(mContext, this); 217 if (!TextUtils.isEmpty(meteredDescription)) { 218 sj.add(meteredDescription); 219 } 220 221 if (!concise && isVerboseSummaryEnabled()) { 222 String verboseSummary = getVerboseSummary(this); 223 if (!TextUtils.isEmpty(verboseSummary)) { 224 sj.add(verboseSummary); 225 } 226 } 227 228 return sj.toString(); 229 } 230 231 @Override shouldShowSsid()232 public boolean shouldShowSsid() { 233 return true; 234 } 235 236 @Override getSsid()237 public synchronized String getSsid() { 238 if (mWifiInfo != null) { 239 return sanitizeSsid(mWifiInfo.getSSID()); 240 } 241 242 return mWifiConfig != null ? sanitizeSsid(mWifiConfig.SSID) : null; 243 } 244 getAllUtf8Ssids()245 synchronized Set<String> getAllUtf8Ssids() { 246 Set<String> allSsids = new ArraySet<>(); 247 for (ScanResult scan : mCurrentHomeScanResults) { 248 allSsids.add(scan.SSID); 249 } 250 for (ScanResult scan : mCurrentRoamingScanResults) { 251 allSsids.add(scan.SSID); 252 } 253 return allSsids; 254 } 255 256 @Override getSecurityTypes()257 public synchronized List<Integer> getSecurityTypes() { 258 return new ArrayList<>(mTargetSecurityTypes); 259 } 260 261 @Override 262 @SuppressLint("HardwareIds") getMacAddress()263 public synchronized String getMacAddress() { 264 if (mWifiInfo != null) { 265 final String wifiInfoMac = mWifiInfo.getMacAddress(); 266 if (!TextUtils.isEmpty(wifiInfoMac) 267 && !TextUtils.equals(wifiInfoMac, DEFAULT_MAC_ADDRESS)) { 268 return wifiInfoMac; 269 } 270 } 271 if (mWifiConfig == null || getPrivacy() != PRIVACY_RANDOMIZED_MAC) { 272 final String[] factoryMacs = mWifiManager.getFactoryMacAddresses(); 273 if (factoryMacs.length > 0) { 274 return factoryMacs[0]; 275 } 276 return null; 277 } 278 return mWifiConfig.getRandomizedMacAddress().toString(); 279 } 280 281 @Override isMetered()282 public synchronized boolean isMetered() { 283 return getMeteredChoice() == METERED_CHOICE_METERED 284 || (mWifiConfig != null && mWifiConfig.meteredHint); 285 } 286 287 @Override isSuggestion()288 public synchronized boolean isSuggestion() { 289 return mWifiConfig != null && mWifiConfig.fromWifiNetworkSuggestion; 290 } 291 292 @Override isSubscription()293 public synchronized boolean isSubscription() { 294 return mPasspointConfig != null; 295 } 296 297 @Override canConnect()298 public synchronized boolean canConnect() { 299 if (isExpired()) { 300 return mOsuWifiEntry != null && mOsuWifiEntry.canConnect(); 301 } 302 303 return mLevel != WIFI_LEVEL_UNREACHABLE 304 && getConnectedState() == CONNECTED_STATE_DISCONNECTED && mWifiConfig != null; 305 } 306 307 @Override connect(@ullable ConnectCallback callback)308 public synchronized void connect(@Nullable ConnectCallback callback) { 309 if (isExpired()) { 310 if (mOsuWifiEntry != null) { 311 mOsuWifiEntry.connect(callback); 312 return; 313 } 314 } 315 // We should flag this network to auto-open captive portal since this method represents 316 // the user manually connecting to a network (i.e. not auto-join). 317 mShouldAutoOpenCaptivePortal = true; 318 mConnectCallback = callback; 319 320 if (mWifiConfig == null) { 321 // We should not be able to call connect() if mWifiConfig is null 322 new ConnectActionListener().onFailure(0); 323 } 324 mWifiManager.stopRestrictingAutoJoinToSubscriptionId(); 325 mWifiManager.connect(mWifiConfig, new ConnectActionListener()); 326 } 327 328 @Override canDisconnect()329 public boolean canDisconnect() { 330 return getConnectedState() == CONNECTED_STATE_CONNECTED; 331 } 332 333 @Override disconnect(@ullable DisconnectCallback callback)334 public synchronized void disconnect(@Nullable DisconnectCallback callback) { 335 if (canDisconnect()) { 336 mCalledDisconnect = true; 337 mDisconnectCallback = callback; 338 mCallbackHandler.postDelayed(() -> { 339 if (callback != null && mCalledDisconnect) { 340 callback.onDisconnectResult( 341 DisconnectCallback.DISCONNECT_STATUS_FAILURE_UNKNOWN); 342 } 343 }, 10_000 /* delayMillis */); 344 mWifiManager.disableEphemeralNetwork(mFqdn); 345 mWifiManager.disconnect(); 346 } 347 } 348 349 @Override canForget()350 public synchronized boolean canForget() { 351 return !isSuggestion() && mPasspointConfig != null; 352 } 353 354 @Override forget(@ullable ForgetCallback callback)355 public synchronized void forget(@Nullable ForgetCallback callback) { 356 if (!canForget()) { 357 return; 358 } 359 360 mForgetCallback = callback; 361 mWifiManager.removePasspointConfiguration(mPasspointConfig.getHomeSp().getFqdn()); 362 new ForgetActionListener().onSuccess(); 363 } 364 365 @Override 366 @MeteredChoice getMeteredChoice()367 public synchronized int getMeteredChoice() { 368 if (mMeteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) { 369 return METERED_CHOICE_METERED; 370 } else if (mMeteredOverride == WifiConfiguration.METERED_OVERRIDE_NOT_METERED) { 371 return METERED_CHOICE_UNMETERED; 372 } 373 return METERED_CHOICE_AUTO; 374 } 375 376 @Override canSetMeteredChoice()377 public synchronized boolean canSetMeteredChoice() { 378 return !isSuggestion() && mPasspointConfig != null; 379 } 380 381 @Override setMeteredChoice(int meteredChoice)382 public synchronized void setMeteredChoice(int meteredChoice) { 383 if (mPasspointConfig == null || !canSetMeteredChoice()) { 384 return; 385 } 386 387 switch (meteredChoice) { 388 case METERED_CHOICE_AUTO: 389 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE; 390 break; 391 case METERED_CHOICE_METERED: 392 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; 393 break; 394 case METERED_CHOICE_UNMETERED: 395 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED; 396 break; 397 default: 398 // Do nothing. 399 return; 400 } 401 mWifiManager.setPasspointMeteredOverride(mPasspointConfig.getHomeSp().getFqdn(), 402 mMeteredOverride); 403 } 404 405 @Override canSetPrivacy()406 public synchronized boolean canSetPrivacy() { 407 return !isSuggestion() && mPasspointConfig != null; 408 } 409 410 @Override 411 @Privacy getPrivacy()412 public synchronized int getPrivacy() { 413 if (mPasspointConfig == null) { 414 return PRIVACY_RANDOMIZED_MAC; 415 } 416 417 return mPasspointConfig.isMacRandomizationEnabled() 418 ? PRIVACY_RANDOMIZED_MAC : PRIVACY_DEVICE_MAC; 419 } 420 421 @Override setPrivacy(int privacy)422 public synchronized void setPrivacy(int privacy) { 423 if (mPasspointConfig == null || !canSetPrivacy()) { 424 return; 425 } 426 427 mWifiManager.setMacRandomizationSettingPasspointEnabled( 428 mPasspointConfig.getHomeSp().getFqdn(), 429 privacy == PRIVACY_DEVICE_MAC ? false : true); 430 } 431 432 @Override isAutoJoinEnabled()433 public synchronized boolean isAutoJoinEnabled() { 434 // Suggestion network; use WifiConfig instead 435 if (mPasspointConfig != null) { 436 return mPasspointConfig.isAutojoinEnabled(); 437 } 438 if (mWifiConfig != null) { 439 return mWifiConfig.allowAutojoin; 440 } 441 return false; 442 } 443 444 @Override canSetAutoJoinEnabled()445 public synchronized boolean canSetAutoJoinEnabled() { 446 return mPasspointConfig != null || mWifiConfig != null; 447 } 448 449 @Override setAutoJoinEnabled(boolean enabled)450 public synchronized void setAutoJoinEnabled(boolean enabled) { 451 if (mPasspointConfig != null) { 452 mWifiManager.allowAutojoinPasspoint(mPasspointConfig.getHomeSp().getFqdn(), enabled); 453 } else if (mWifiConfig != null) { 454 mWifiManager.allowAutojoin(mWifiConfig.networkId, enabled); 455 } 456 } 457 458 @Override getSecurityString(boolean concise)459 public String getSecurityString(boolean concise) { 460 return mContext.getString(R.string.wifitrackerlib_wifi_security_passpoint); 461 } 462 463 @Override getStandardString()464 public synchronized String getStandardString() { 465 if (mWifiInfo != null) { 466 return Utils.getStandardString(mContext, mWifiInfo.getWifiStandard()); 467 } 468 if (!mCurrentHomeScanResults.isEmpty()) { 469 return Utils.getStandardString( 470 mContext, mCurrentHomeScanResults.get(0).getWifiStandard()); 471 } 472 if (!mCurrentRoamingScanResults.isEmpty()) { 473 return Utils.getStandardString( 474 mContext, mCurrentRoamingScanResults.get(0).getWifiStandard()); 475 } 476 return ""; 477 } 478 479 @Override getBandString()480 public synchronized String getBandString() { 481 if (mWifiInfo != null) { 482 return Utils.wifiInfoToBandString(mContext, mWifiInfo); 483 } 484 if (!mCurrentHomeScanResults.isEmpty()) { 485 return Utils.frequencyToBandString(mContext, mCurrentHomeScanResults.get(0).frequency); 486 } 487 if (!mCurrentRoamingScanResults.isEmpty()) { 488 return Utils.frequencyToBandString( 489 mContext, mCurrentRoamingScanResults.get(0).frequency); 490 } 491 return ""; 492 } 493 494 @Override isExpired()495 public synchronized boolean isExpired() { 496 if (mSubscriptionExpirationTimeInMillis <= 0) { 497 // Expiration time not specified. 498 return false; 499 } else { 500 return System.currentTimeMillis() >= mSubscriptionExpirationTimeInMillis; 501 } 502 } 503 504 @WorkerThread updatePasspointConfig(@ullable PasspointConfiguration passpointConfig)505 synchronized void updatePasspointConfig(@Nullable PasspointConfiguration passpointConfig) { 506 mPasspointConfig = passpointConfig; 507 if (mPasspointConfig != null) { 508 mSubscriptionExpirationTimeInMillis = 509 passpointConfig.getSubscriptionExpirationTimeMillis(); 510 mMeteredOverride = passpointConfig.getMeteredOverride(); 511 } 512 notifyOnUpdated(); 513 } 514 515 @WorkerThread updateScanResultInfo(@ullable WifiConfiguration wifiConfig, @Nullable List<ScanResult> homeScanResults, @Nullable List<ScanResult> roamingScanResults)516 synchronized void updateScanResultInfo(@Nullable WifiConfiguration wifiConfig, 517 @Nullable List<ScanResult> homeScanResults, 518 @Nullable List<ScanResult> roamingScanResults) 519 throws IllegalArgumentException { 520 mWifiConfig = wifiConfig; 521 mCurrentHomeScanResults.clear(); 522 mCurrentRoamingScanResults.clear(); 523 if (homeScanResults != null) { 524 mCurrentHomeScanResults.addAll(homeScanResults); 525 } 526 if (roamingScanResults != null) { 527 mCurrentRoamingScanResults.addAll(roamingScanResults); 528 } 529 if (mWifiConfig != null) { 530 List<ScanResult> currentScanResults = new ArrayList<>(); 531 if (homeScanResults != null && !homeScanResults.isEmpty()) { 532 currentScanResults.addAll(homeScanResults); 533 } else if (roamingScanResults != null && !roamingScanResults.isEmpty()) { 534 currentScanResults.addAll(roamingScanResults); 535 } 536 ScanResult bestScanResult = getBestScanResultByLevel(currentScanResults); 537 if (bestScanResult != null) { 538 mWifiConfig.SSID = "\"" + bestScanResult.SSID + "\""; 539 } 540 if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) { 541 mLevel = bestScanResult != null 542 ? mWifiManager.calculateSignalLevel(bestScanResult.level) 543 : WIFI_LEVEL_UNREACHABLE; 544 } 545 } else { 546 mLevel = WIFI_LEVEL_UNREACHABLE; 547 } 548 notifyOnUpdated(); 549 } 550 551 @Override updateSecurityTypes()552 protected synchronized void updateSecurityTypes() { 553 if (mWifiInfo != null) { 554 final int wifiInfoSecurity = mWifiInfo.getCurrentSecurityType(); 555 if (wifiInfoSecurity != SECURITY_TYPE_UNKNOWN) { 556 mTargetSecurityTypes = Collections.singletonList(wifiInfoSecurity); 557 return; 558 } 559 } 560 } 561 562 @WorkerThread 563 @Override connectionInfoMatches(@onNull WifiInfo wifiInfo)564 protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo) { 565 if (!wifiInfo.isPasspointAp()) { 566 return false; 567 } 568 569 if (BuildCompat.isAtLeastV() && NonSdkApiWrapper.isAndroidVWifiApiEnabled()) { 570 return TextUtils.equals(mUniqueId, wifiInfo.getPasspointUniqueId()); 571 } 572 573 // Match with FQDN if WifiInfo doesn't support returning the uniqueID. 574 return TextUtils.equals(wifiInfo.getPasspointFqdn(), mFqdn); 575 } 576 577 @WorkerThread 578 @Override onNetworkCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities capabilities)579 synchronized void onNetworkCapabilitiesChanged( 580 @NonNull Network network, @NonNull NetworkCapabilities capabilities) { 581 super.onNetworkCapabilitiesChanged(network, capabilities); 582 583 // Auto-open an available captive portal if the user manually connected to this network. 584 if (canSignIn() && mShouldAutoOpenCaptivePortal) { 585 mShouldAutoOpenCaptivePortal = false; 586 signIn(null /* callback */); 587 } 588 } 589 590 @NonNull uniqueIdToPasspointWifiEntryKey(@onNull String uniqueId)591 static String uniqueIdToPasspointWifiEntryKey(@NonNull String uniqueId) { 592 checkNotNull(uniqueId, "Cannot create key with null unique id!"); 593 return KEY_PREFIX + uniqueId; 594 } 595 596 @Override getScanResultDescription()597 protected String getScanResultDescription() { 598 // TODO(b/70983952): Fill this method in. 599 return ""; 600 } 601 602 @Override getNetworkSelectionDescription()603 synchronized String getNetworkSelectionDescription() { 604 return Utils.getNetworkSelectionDescription(mWifiConfig); 605 } 606 607 /** Pass a reference to a matching OsuWifiEntry for expiration handling */ setOsuWifiEntry(OsuWifiEntry osuWifiEntry)608 synchronized void setOsuWifiEntry(OsuWifiEntry osuWifiEntry) { 609 mOsuWifiEntry = osuWifiEntry; 610 if (mOsuWifiEntry != null) { 611 mOsuWifiEntry.setListener(this); 612 } 613 } 614 615 /** Callback for updates to the linked OsuWifiEntry */ 616 @Override onUpdated()617 public void onUpdated() { 618 notifyOnUpdated(); 619 } 620 621 @Override canSignIn()622 public synchronized boolean canSignIn() { 623 return mNetwork != null 624 && mNetworkCapabilities != null 625 && mNetworkCapabilities.hasCapability( 626 NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); 627 } 628 629 @Override signIn(@ullable SignInCallback callback)630 public void signIn(@Nullable SignInCallback callback) { 631 if (canSignIn()) { 632 NonSdkApiWrapper.startCaptivePortalApp( 633 mContext.getSystemService(ConnectivityManager.class), mNetwork); 634 } 635 } 636 637 /** Get the PasspointConfiguration instance of the entry. */ getPasspointConfig()638 public PasspointConfiguration getPasspointConfig() { 639 return mPasspointConfig; 640 } 641 642 @Override toString()643 public String toString() { 644 StringJoiner sj = new StringJoiner("][", "[", "]"); 645 sj.add("FQDN:" + mFqdn); 646 sj.add("FriendlyName:" + mFriendlyName); 647 if (mPasspointConfig != null) { 648 sj.add("UniqueId:" + mPasspointConfig.getUniqueId()); 649 } else if (mWifiConfig != null) { 650 sj.add("UniqueId:" + mWifiConfig.getKey()); 651 } 652 return super.toString() + sj; 653 } 654 } 655