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