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