1 /* 2 * Copyright (C) 2016 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.server.wifi.hotspot2; 18 19 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE; 20 import static android.net.wifi.WifiConfiguration.MeteredOverride; 21 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS; 22 23 import static java.security.cert.PKIXReason.NO_TRUST_ANCHOR; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.ActivityManager; 28 import android.app.AppOpsManager; 29 import android.content.Context; 30 import android.net.MacAddress; 31 import android.net.wifi.ScanResult; 32 import android.net.wifi.WifiConfiguration; 33 import android.net.wifi.WifiEnterpriseConfig; 34 import android.net.wifi.WifiManager; 35 import android.net.wifi.WifiSsid; 36 import android.net.wifi.hotspot2.IProvisioningCallback; 37 import android.net.wifi.hotspot2.OsuProvider; 38 import android.net.wifi.hotspot2.PasspointConfiguration; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.os.Process; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.util.Pair; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.modules.utils.build.SdkLevel; 48 import com.android.server.wifi.Clock; 49 import com.android.server.wifi.MacAddressUtil; 50 import com.android.server.wifi.NetworkUpdateResult; 51 import com.android.server.wifi.WifiCarrierInfoManager; 52 import com.android.server.wifi.WifiConfigManager; 53 import com.android.server.wifi.WifiConfigStore; 54 import com.android.server.wifi.WifiInjector; 55 import com.android.server.wifi.WifiKeyStore; 56 import com.android.server.wifi.WifiMetrics; 57 import com.android.server.wifi.WifiNative; 58 import com.android.server.wifi.WifiSettingsStore; 59 import com.android.server.wifi.hotspot2.anqp.ANQPElement; 60 import com.android.server.wifi.hotspot2.anqp.Constants; 61 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement; 62 import com.android.server.wifi.hotspot2.anqp.I18Name; 63 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo; 64 import com.android.server.wifi.hotspot2.anqp.VenueNameElement; 65 import com.android.server.wifi.hotspot2.anqp.VenueUrlElement; 66 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent; 67 import com.android.server.wifi.util.InformationElementUtil; 68 import com.android.server.wifi.util.WifiPermissionsUtil; 69 70 import java.io.IOException; 71 import java.io.PrintWriter; 72 import java.net.URL; 73 import java.security.GeneralSecurityException; 74 import java.security.KeyStore; 75 import java.security.cert.CertPath; 76 import java.security.cert.CertPathValidator; 77 import java.security.cert.CertPathValidatorException; 78 import java.security.cert.CertificateFactory; 79 import java.security.cert.PKIXParameters; 80 import java.security.cert.X509Certificate; 81 import java.util.ArrayList; 82 import java.util.Arrays; 83 import java.util.Collections; 84 import java.util.HashMap; 85 import java.util.HashSet; 86 import java.util.List; 87 import java.util.Locale; 88 import java.util.Map; 89 import java.util.Set; 90 import java.util.stream.Collectors; 91 92 /** 93 * This class provides the APIs to manage Passpoint provider configurations. 94 * It deals with the following: 95 * - Maintaining a list of configured Passpoint providers for provider matching. 96 * - Persisting the providers configurations to store when required. 97 * - matching Passpoint providers based on the scan results 98 * - Supporting WifiManager Public API calls: 99 * > addOrUpdatePasspointConfiguration() 100 * > removePasspointConfiguration() 101 * > getPasspointConfigurations() 102 * 103 * The provider matching requires obtaining additional information from the AP (ANQP elements). 104 * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests. 105 * 106 * NOTE: These API's are not thread safe and should only be used from the main Wifi thread. 107 */ 108 public class PasspointManager { 109 private static final String TAG = "PasspointManager"; 110 111 /** 112 * Handle for the current {@link PasspointManager} instance. This is needed to avoid 113 * circular dependency with the WifiConfigManger, it will be used for adding the 114 * legacy Passpoint configurations. 115 * 116 * This can be eliminated once we can remove the dependency for WifiConfigManager (for 117 * triggering config store write) from this class. 118 */ 119 private static PasspointManager sPasspointManager; 120 121 private final PasspointEventHandler mPasspointEventHandler; 122 private final WifiInjector mWifiInjector; 123 private final Handler mHandler; 124 private final WifiKeyStore mKeyStore; 125 private final PasspointObjectFactory mObjectFactory; 126 127 private final Map<String, PasspointProvider> mProviders; 128 private final AnqpCache mAnqpCache; 129 private final ANQPRequestManager mAnqpRequestManager; 130 private final WifiConfigManager mWifiConfigManager; 131 private final WifiMetrics mWifiMetrics; 132 private final PasspointProvisioner mPasspointProvisioner; 133 private PasspointNetworkNominateHelper mPasspointNetworkNominateHelper; 134 private final AppOpsManager mAppOps; 135 private final WifiCarrierInfoManager mWifiCarrierInfoManager; 136 private final MacAddressUtil mMacAddressUtil; 137 private final Clock mClock; 138 private final WifiPermissionsUtil mWifiPermissionsUtil; 139 private final WifiSettingsStore mSettingsStore; 140 private final boolean mIsLowMemory; 141 142 /** 143 * Map of package name of an app to the app ops changed listener for the app. 144 */ 145 private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>(); 146 147 // Counter used for assigning unique identifier to each provider. 148 private long mProviderIndex; 149 private boolean mVerboseLoggingEnabled = false; 150 // Set default value to false before receiving boot completed event. 151 private boolean mEnabled = false; 152 153 private class CallbackHandler implements PasspointEventHandler.Callbacks { 154 private final Context mContext; CallbackHandler(Context context)155 CallbackHandler(Context context) { 156 mContext = context; 157 } 158 159 @Override onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements)160 public void onANQPResponse(long bssid, 161 Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 162 if (mVerboseLoggingEnabled) { 163 Log.d(TAG, "ANQP response received from BSSID " 164 + Utils.macToString(bssid) + " - List of ANQP elements:"); 165 int i = 0; 166 for (Constants.ANQPElementType type : anqpElements.keySet()) { 167 Log.d(TAG, "#" + i++ + ": " + type); 168 } 169 } 170 // Notify request manager for the completion of a request. 171 ANQPNetworkKey anqpKey = 172 mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null); 173 if (anqpElements == null || anqpKey == null) { 174 // Query failed or the request wasn't originated from us (not tracked by the 175 // request manager). Nothing to be done. 176 return; 177 } 178 179 if (anqpElements.containsKey(Constants.ANQPElementType.ANQPVenueUrl)) { 180 // Venue URL ANQP is requested and received only after the network is connected 181 mWifiMetrics.incrementTotalNumberOfPasspointConnectionsWithVenueUrl(); 182 } 183 184 // Add new entry to the cache. 185 mAnqpCache.addOrUpdateEntry(anqpKey, anqpElements); 186 } 187 188 @Override onIconResponse(long bssid, String fileName, byte[] data)189 public void onIconResponse(long bssid, String fileName, byte[] data) { 190 // Empty 191 } 192 193 @Override onWnmFrameReceived(WnmData event)194 public void onWnmFrameReceived(WnmData event) { 195 // Empty 196 } 197 } 198 199 /** 200 * Data provider for the Passpoint configuration store data 201 * {@link PasspointConfigUserStoreData}. 202 */ 203 private class UserDataSourceHandler implements PasspointConfigUserStoreData.DataSource { 204 @Override getProviders()205 public List<PasspointProvider> getProviders() { 206 List<PasspointProvider> providers = new ArrayList<>(); 207 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 208 providers.add(entry.getValue()); 209 } 210 return providers; 211 } 212 213 @Override setProviders(List<PasspointProvider> providers)214 public void setProviders(List<PasspointProvider> providers) { 215 mProviders.clear(); 216 for (PasspointProvider provider : providers) { 217 provider.enableVerboseLogging(mVerboseLoggingEnabled); 218 mProviders.put(provider.getConfig().getUniqueId(), provider); 219 if (provider.getPackageName() != null) { 220 startTrackingAppOpsChange(provider.getPackageName(), 221 provider.getCreatorUid()); 222 } 223 } 224 } 225 } 226 227 /** 228 * Data provider for the Passpoint configuration store data 229 * {@link PasspointConfigSharedStoreData}. 230 */ 231 private class SharedDataSourceHandler implements PasspointConfigSharedStoreData.DataSource { 232 @Override getProviderIndex()233 public long getProviderIndex() { 234 return mProviderIndex; 235 } 236 237 @Override setProviderIndex(long providerIndex)238 public void setProviderIndex(long providerIndex) { 239 mProviderIndex = providerIndex; 240 } 241 } 242 243 /** 244 * Listener for app-ops changes for apps to remove the corresponding Passpoint profiles. 245 */ 246 private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener { 247 private final String mPackageName; 248 private final int mUid; 249 AppOpsChangedListener(@onNull String packageName, int uid)250 AppOpsChangedListener(@NonNull String packageName, int uid) { 251 mPackageName = packageName; 252 mUid = uid; 253 } 254 255 @Override onOpChanged(String op, String packageName)256 public void onOpChanged(String op, String packageName) { 257 mHandler.post(() -> { 258 if (!mPackageName.equals(packageName)) return; 259 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return; 260 261 // Ensures the uid to package mapping is still correct. 262 try { 263 mAppOps.checkPackage(mUid, mPackageName); 264 } catch (SecurityException e) { 265 Log.wtf(TAG, "Invalid uid/package" + packageName); 266 return; 267 } 268 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName) 269 == AppOpsManager.MODE_IGNORED) { 270 Log.i(TAG, "User disallowed change wifi state for " + packageName); 271 272 // Removes the profiles installed by the app from database. 273 removePasspointProviderWithPackage(mPackageName); 274 } 275 }); 276 } 277 } 278 279 private class OnNetworkUpdateListener implements 280 WifiConfigManager.OnNetworkUpdateListener { 281 @Override onConnectChoiceSet(@onNull List<WifiConfiguration> networks, String choiceKey, int rssi)282 public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks, 283 String choiceKey, int rssi) { 284 onUserConnectChoiceSet(networks, choiceKey, rssi); 285 } 286 @Override onConnectChoiceRemoved(String choiceKey)287 public void onConnectChoiceRemoved(String choiceKey) { 288 onUserConnectChoiceRemove(choiceKey); 289 } 290 291 } 292 onUserConnectChoiceRemove(String choiceKey)293 private void onUserConnectChoiceRemove(String choiceKey) { 294 mProviders.values().stream() 295 .filter(provider -> TextUtils.equals(provider.getConnectChoice(), choiceKey)) 296 .forEach(provider -> { 297 provider.setUserConnectChoice(null, 0); 298 }); 299 mWifiConfigManager.saveToStore(true); 300 } 301 onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey, int rssi)302 private void onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey, 303 int rssi) { 304 for (WifiConfiguration config : networks) { 305 PasspointProvider provider = mProviders.get(config.getProfileKey()); 306 if (provider != null) { 307 provider.setUserConnectChoice(choiceKey, rssi); 308 } 309 } 310 PasspointProvider provider = mProviders.get(choiceKey); 311 if (provider != null) { 312 provider.setUserConnectChoice(null, 0); 313 } 314 mWifiConfigManager.saveToStore(true); 315 } 316 317 /** 318 * Remove all Passpoint profiles installed by the app that has been disabled or uninstalled. 319 * 320 * @param packageName Package name of the app to remove the corresponding Passpoint profiles. 321 */ removePasspointProviderWithPackage(@onNull String packageName)322 public void removePasspointProviderWithPackage(@NonNull String packageName) { 323 stopTrackingAppOpsChange(packageName); 324 for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage( 325 packageName).entrySet()) { 326 String uniqueId = entry.getValue().getConfig().getUniqueId(); 327 removeProvider(Process.WIFI_UID /* ignored */, true, uniqueId, null); 328 } 329 } 330 getPasspointProviderWithPackage( @onNull String packageName)331 private Map<String, PasspointProvider> getPasspointProviderWithPackage( 332 @NonNull String packageName) { 333 return mProviders.entrySet().stream().filter( 334 entry -> TextUtils.equals(packageName, 335 entry.getValue().getPackageName())).collect( 336 Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); 337 } 338 startTrackingAppOpsChange(@onNull String packageName, int uid)339 private void startTrackingAppOpsChange(@NonNull String packageName, int uid) { 340 // The package is already registered. 341 if (mAppOpsChangedListenerPerApp.containsKey(packageName)) return; 342 AppOpsChangedListener appOpsChangedListener = new AppOpsChangedListener(packageName, uid); 343 mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener); 344 mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener); 345 } 346 stopTrackingAppOpsChange(@onNull String packageName)347 private void stopTrackingAppOpsChange(@NonNull String packageName) { 348 AppOpsChangedListener appOpsChangedListener = mAppOpsChangedListenerPerApp.remove( 349 packageName); 350 if (appOpsChangedListener == null) { 351 Log.i(TAG, "No app ops listener found for " + packageName); 352 return; 353 } 354 mAppOps.stopWatchingMode(appOpsChangedListener); 355 } 356 PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiSettingsStore wifiSettingsStore, WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, MacAddressUtil macAddressUtil, WifiPermissionsUtil wifiPermissionsUtil)357 public PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, 358 WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, 359 PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, 360 WifiConfigStore wifiConfigStore, 361 WifiSettingsStore wifiSettingsStore, 362 WifiMetrics wifiMetrics, 363 WifiCarrierInfoManager wifiCarrierInfoManager, 364 MacAddressUtil macAddressUtil, 365 WifiPermissionsUtil wifiPermissionsUtil) { 366 mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiInjector, 367 new CallbackHandler(context)); 368 mWifiInjector = wifiInjector; 369 mHandler = handler; 370 mKeyStore = keyStore; 371 mObjectFactory = objectFactory; 372 mProviders = new HashMap<>(); 373 mAnqpCache = objectFactory.makeAnqpCache(clock); 374 mAnqpRequestManager = objectFactory.makeANQPRequestManager(mPasspointEventHandler, clock); 375 mWifiConfigManager = wifiConfigManager; 376 mWifiMetrics = wifiMetrics; 377 mProviderIndex = 0; 378 mWifiCarrierInfoManager = wifiCarrierInfoManager; 379 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigUserStoreData( 380 mKeyStore, mWifiCarrierInfoManager, new UserDataSourceHandler(), clock)); 381 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigSharedStoreData( 382 new SharedDataSourceHandler())); 383 mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, wifiNative, 384 this, wifiMetrics); 385 ActivityManager activityManager = context.getSystemService(ActivityManager.class); 386 mIsLowMemory = activityManager.isLowRamDevice(); 387 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 388 sPasspointManager = this; 389 mMacAddressUtil = macAddressUtil; 390 mClock = clock; 391 mHandler.postAtFrontOfQueue(() -> 392 mWifiConfigManager.addOnNetworkUpdateListener( 393 new PasspointManager.OnNetworkUpdateListener())); 394 mWifiPermissionsUtil = wifiPermissionsUtil; 395 mSettingsStore = wifiSettingsStore; 396 mEnabled = mSettingsStore.isWifiPasspointEnabled(); 397 } 398 399 /** 400 * Initializes the provisioning flow with a looper. 401 * This looper should be tied to a background worker thread since PasspointProvisioner has a 402 * heavy workload. 403 */ initializeProvisioner(Looper looper)404 public void initializeProvisioner(Looper looper) { 405 mPasspointProvisioner.init(looper); 406 } 407 408 /** 409 * Sets the {@link PasspointNetworkNominateHelper} used by this PasspointManager. 410 */ setPasspointNetworkNominateHelper( @ullable PasspointNetworkNominateHelper nominateHelper)411 public void setPasspointNetworkNominateHelper( 412 @Nullable PasspointNetworkNominateHelper nominateHelper) { 413 mPasspointNetworkNominateHelper = nominateHelper; 414 } 415 416 /** 417 * Enable verbose logging 418 * @param verbose enables verbose logging 419 */ enableVerboseLogging(boolean verbose)420 public void enableVerboseLogging(boolean verbose) { 421 mVerboseLoggingEnabled = verbose; 422 mPasspointProvisioner.enableVerboseLogging(verbose); 423 for (PasspointProvider provider : mProviders.values()) { 424 provider.enableVerboseLogging(verbose); 425 } 426 } 427 updateWifiConfigInWcmIfPresent( WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion)428 private void updateWifiConfigInWcmIfPresent( 429 WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion) { 430 WifiConfiguration configInWcm = 431 mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey()); 432 if (configInWcm == null) return; 433 // suggestion != saved 434 if (isFromSuggestion != configInWcm.fromWifiNetworkSuggestion) return; 435 // is suggestion from same app. 436 if (isFromSuggestion 437 && (configInWcm.creatorUid != uid 438 || !TextUtils.equals(configInWcm.creatorName, packageName))) { 439 return; 440 } 441 NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork( 442 newConfig, uid, packageName, false); 443 if (!result.isSuccess()) { 444 Log.e(TAG, "Failed to update config in WifiConfigManager"); 445 } else { 446 mWifiConfigManager.allowAutojoin(result.getNetworkId(), newConfig.allowAutojoin); 447 if (mVerboseLoggingEnabled) { 448 Log.v(TAG, "Updated config in WifiConfigManager"); 449 } 450 } 451 } 452 453 /** 454 * Add or update a Passpoint provider with the given configuration. 455 * 456 * Each provider is uniquely identified by its unique identifier, see 457 * {@link PasspointConfiguration#getUniqueId()}. 458 * In the case when there is an existing configuration with the same unique identifier, 459 * a provider with the new configuration will replace the existing provider. 460 * 461 * @param config Configuration of the Passpoint provider to be added 462 * @param uid Uid of the app adding/Updating {@code config} 463 * @param packageName Package name of the app adding/Updating {@code config} 464 * @param isFromSuggestion Whether this {@code config} is from suggestion API 465 * @param isTrusted Whether this {@code config} an trusted network, default should be true. 466 * Only able set to false when {@code isFromSuggestion} is true, otherwise 467 * adding {@code config} will be false. 468 * @return true if provider is added, false otherwise 469 */ addOrUpdateProvider(PasspointConfiguration config, int uid, String packageName, boolean isFromSuggestion, boolean isTrusted)470 public boolean addOrUpdateProvider(PasspointConfiguration config, int uid, 471 String packageName, boolean isFromSuggestion, boolean isTrusted) { 472 mWifiMetrics.incrementNumPasspointProviderInstallation(); 473 if (config == null) { 474 Log.e(TAG, "Configuration not provided"); 475 return false; 476 } 477 if (!config.validate()) { 478 Log.e(TAG, "Invalid configuration"); 479 return false; 480 } 481 if (!(isFromSuggestion || isTrusted)) { 482 Log.e(TAG, "Set isTrusted to false on a non suggestion passpoint is not allowed"); 483 return false; 484 } 485 if (config.getServiceFriendlyNames() != null && isFromSuggestion) { 486 Log.e(TAG, "Passpoint from suggestion should not have ServiceFriendlyNames"); 487 return false; 488 } 489 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { 490 Log.e(TAG, "UID " + uid + " not visible to the current user"); 491 return false; 492 } 493 if (getPasspointProviderWithPackage(packageName).size() 494 >= WifiManager.getMaxNumberOfNetworkSuggestionsPerApp(mIsLowMemory)) { 495 Log.e(TAG, "packageName " + packageName + " has too many passpoint with exceed the " 496 + "limitation"); 497 return false; 498 } 499 500 mWifiCarrierInfoManager.tryUpdateCarrierIdForPasspoint(config); 501 // Create a provider and install the necessary certificates and keys. 502 PasspointProvider newProvider = mObjectFactory.makePasspointProvider(config, mKeyStore, 503 mWifiCarrierInfoManager, mProviderIndex++, uid, packageName, isFromSuggestion, 504 mClock); 505 newProvider.setTrusted(isTrusted); 506 507 boolean metricsNoRootCa = false; 508 boolean metricsSelfSignedRootCa = false; 509 boolean metricsSubscriptionExpiration = false; 510 511 if (config.getCredential().getUserCredential() != null 512 || config.getCredential().getCertCredential() != null) { 513 X509Certificate[] x509Certificates = config.getCredential().getCaCertificates(); 514 if (x509Certificates == null) { 515 metricsNoRootCa = true; 516 } else { 517 try { 518 for (X509Certificate certificate : x509Certificates) { 519 verifyCaCert(certificate); 520 } 521 } catch (CertPathValidatorException e) { 522 // A self signed Root CA will fail path validation checks with NO_TRUST_ANCHOR 523 if (e.getReason() == NO_TRUST_ANCHOR) { 524 metricsSelfSignedRootCa = true; 525 } 526 } catch (Exception e) { 527 // Other exceptions, fall through, will be handled below 528 } 529 } 530 } 531 if (config.getSubscriptionExpirationTimeMillis() != Long.MIN_VALUE) { 532 metricsSubscriptionExpiration = true; 533 } 534 535 if (!newProvider.installCertsAndKeys()) { 536 Log.e(TAG, "Failed to install certificates and keys to keystore"); 537 return false; 538 } 539 540 // Remove existing provider with the same unique ID. 541 if (mProviders.containsKey(config.getUniqueId())) { 542 PasspointProvider old = mProviders.get(config.getUniqueId()); 543 // If new profile is from suggestion and from a different App, ignore new profile, 544 // return false. 545 // If from same app, update it. 546 if (isFromSuggestion && !old.getPackageName().equals(packageName)) { 547 newProvider.uninstallCertsAndKeys(); 548 return false; 549 } 550 Log.d(TAG, "Replacing configuration for FQDN: " + config.getHomeSp().getFqdn() 551 + " and unique ID: " + config.getUniqueId()); 552 old.uninstallCertsAndKeys(); 553 mProviders.remove(config.getUniqueId()); 554 // Keep the user connect choice and AnonymousIdentity 555 newProvider.setUserConnectChoice(old.getConnectChoice(), old.getConnectChoiceRssi()); 556 newProvider.setAnonymousIdentity(old.getAnonymousIdentity()); 557 // New profile changes the credential, remove the related WifiConfig. 558 if (!old.equals(newProvider)) { 559 mWifiConfigManager.removePasspointConfiguredNetwork( 560 newProvider.getWifiConfig().getProfileKey()); 561 } else { 562 // If there is a config cached in WifiConfigManager, update it with new info. 563 updateWifiConfigInWcmIfPresent( 564 newProvider.getWifiConfig(), uid, packageName, isFromSuggestion); 565 } 566 } 567 newProvider.enableVerboseLogging(mVerboseLoggingEnabled); 568 mProviders.put(config.getUniqueId(), newProvider); 569 if (!isFromSuggestion) { 570 // Suggestions will be handled by the WifiNetworkSuggestionsManager 571 mWifiConfigManager.saveToStore(true /* forceWrite */); 572 } 573 if (!isFromSuggestion && newProvider.getPackageName() != null) { 574 startTrackingAppOpsChange(newProvider.getPackageName(), uid); 575 } 576 Log.d(TAG, "Added/updated Passpoint configuration for FQDN: " 577 + config.getHomeSp().getFqdn() + " with unique ID: " + config.getUniqueId() 578 + " by UID: " + uid); 579 if (metricsNoRootCa) { 580 mWifiMetrics.incrementNumPasspointProviderWithNoRootCa(); 581 } 582 if (metricsSelfSignedRootCa) { 583 mWifiMetrics.incrementNumPasspointProviderWithSelfSignedRootCa(); 584 } 585 if (metricsSubscriptionExpiration) { 586 mWifiMetrics.incrementNumPasspointProviderWithSubscriptionExpiration(); 587 } 588 if (SdkLevel.isAtLeastS() && config.getDecoratedIdentityPrefix() != null) { 589 mWifiMetrics.incrementTotalNumberOfPasspointProfilesWithDecoratedIdentity(); 590 } 591 mWifiMetrics.incrementNumPasspointProviderInstallSuccess(); 592 if (mPasspointNetworkNominateHelper != null) { 593 mPasspointNetworkNominateHelper.updateBestMatchScanDetailForProviders(); 594 } 595 return true; 596 } 597 removeProviderInternal(PasspointProvider provider, int callingUid, boolean privileged)598 private boolean removeProviderInternal(PasspointProvider provider, int callingUid, 599 boolean privileged) { 600 if (!privileged && callingUid != provider.getCreatorUid()) { 601 Log.e(TAG, "UID " + callingUid + " cannot remove profile created by " 602 + provider.getCreatorUid()); 603 return false; 604 } 605 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(callingUid)) { 606 Log.e(TAG, "UID " + callingUid + " not visible to the current user"); 607 return false; 608 } 609 provider.uninstallCertsAndKeys(); 610 String packageName = provider.getPackageName(); 611 if (!provider.isFromSuggestion()) { 612 // Remove non-suggestion configs corresponding to the profile in WifiConfigManager. 613 // Suggestion passpoint will be handled by WifiNetworkSuggestionsManager 614 mWifiConfigManager.removePasspointConfiguredNetwork( 615 provider.getWifiConfig().getProfileKey()); 616 } 617 String uniqueId = provider.getConfig().getUniqueId(); 618 mProviders.remove(uniqueId); 619 mWifiConfigManager.removeConnectChoiceFromAllNetworks(uniqueId); 620 if (!provider.isFromSuggestion()) { 621 // Suggestions will be handled by the WifiNetworkSuggestionsManager 622 mWifiConfigManager.saveToStore(true /* forceWrite */); 623 } 624 625 // Stop monitoring the package if there is no Passpoint profile installed by the package 626 if (mAppOpsChangedListenerPerApp.containsKey(packageName) 627 && getPasspointProviderWithPackage(packageName).size() == 0) { 628 stopTrackingAppOpsChange(packageName); 629 } 630 Log.d(TAG, "Removed Passpoint configuration: " + uniqueId); 631 mWifiMetrics.incrementNumPasspointProviderUninstallSuccess(); 632 return true; 633 } 634 635 /** 636 * Remove a Passpoint provider identified by the given its unique identifier. 637 * 638 * @param callingUid Calling UID. 639 * @param privileged Whether the caller is a privileged entity 640 * @param uniqueId The ID of the provider to remove. Not required if FQDN is specified. 641 * @param fqdn The FQDN of the provider to remove. Not required if unique ID is specified. 642 * @return true if a provider is removed, false otherwise 643 */ removeProvider(int callingUid, boolean privileged, String uniqueId, String fqdn)644 public boolean removeProvider(int callingUid, boolean privileged, String uniqueId, 645 String fqdn) { 646 if (uniqueId == null && fqdn == null) { 647 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 648 Log.e(TAG, "Cannot remove provider, both FQDN and unique ID are null"); 649 return false; 650 } 651 652 if (uniqueId != null) { 653 // Unique identifier provided 654 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 655 PasspointProvider provider = mProviders.get(uniqueId); 656 if (provider == null) { 657 Log.e(TAG, "Config doesn't exist"); 658 return false; 659 } 660 return removeProviderInternal(provider, callingUid, privileged); 661 } 662 663 // FQDN provided, loop through all profiles with matching FQDN 664 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 665 int removedProviders = 0; 666 int numOfUninstallations = 0; 667 for (PasspointProvider provider : passpointProviders) { 668 if (!TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 669 continue; 670 } 671 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 672 numOfUninstallations++; 673 if (removeProviderInternal(provider, callingUid, privileged)) { 674 removedProviders++; 675 } 676 } 677 678 if (numOfUninstallations == 0) { 679 // Update uninstallation requests metrics here to cover the corner case of trying to 680 // uninstall a non-existent provider. 681 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 682 } 683 684 return removedProviders > 0; 685 } 686 687 /** 688 * Enable or disable the auto-join configuration. Auto-join controls whether or not the 689 * passpoint configuration is used for auto connection (network selection). Note that even 690 * when auto-join is disabled the configuration can still be used for manual connection. 691 * 692 * @param uniqueId The unique identifier of the configuration. Not required if FQDN is specified 693 * @param fqdn The FQDN of the configuration. Not required if uniqueId is specified. 694 * @param enableAutojoin true to enable auto-join, false to disable. 695 * @return true on success, false otherwise (e.g. if no such provider exists). 696 */ enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin)697 public boolean enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin) { 698 if (uniqueId == null && fqdn == null) { 699 return false; 700 } 701 if (uniqueId != null) { 702 // Unique identifier provided 703 PasspointProvider provider = mProviders.get(uniqueId); 704 if (provider == null) { 705 Log.e(TAG, "Config doesn't exist"); 706 return false; 707 } 708 if (provider.setAutojoinEnabled(enableAutojoin)) { 709 mWifiMetrics.logUserActionEvent(enableAutojoin 710 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON 711 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF, 712 provider.isFromSuggestion(), true); 713 // Update WifiConfigManager if changed. 714 updateWifiConfigInWcmIfPresent(provider.getWifiConfig(), provider.getCreatorUid(), 715 provider.getPackageName(), provider.isFromSuggestion()); 716 } 717 718 mWifiConfigManager.saveToStore(true); 719 return true; 720 } 721 722 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 723 boolean found = false; 724 725 // FQDN provided, loop through all profiles with matching FQDN 726 for (PasspointProvider provider : passpointProviders) { 727 if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 728 if (provider.setAutojoinEnabled(enableAutojoin)) { 729 mWifiMetrics.logUserActionEvent(enableAutojoin 730 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON 731 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF, 732 provider.isFromSuggestion(), true); 733 // Update WifiConfigManager if changed. 734 updateWifiConfigInWcmIfPresent(provider.getWifiConfig(), 735 provider.getCreatorUid(), provider.getPackageName(), 736 provider.isFromSuggestion()); 737 } 738 found = true; 739 } 740 } 741 if (found) { 742 mWifiConfigManager.saveToStore(true); 743 } 744 return found; 745 } 746 747 /** 748 * Enable or disable MAC randomization for this passpoint profile. 749 * @param fqdn The FQDN of the configuration 750 * @param enable true to enable MAC randomization, false to disable 751 * @return true on success, false otherwise (e.g. if no such provider exists). 752 */ enableMacRandomization(@onNull String fqdn, boolean enable)753 public boolean enableMacRandomization(@NonNull String fqdn, boolean enable) { 754 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 755 boolean found = false; 756 757 // Loop through all profiles with matching FQDN 758 for (PasspointProvider provider : passpointProviders) { 759 if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 760 boolean settingChanged = provider.setMacRandomizationEnabled(enable); 761 if (settingChanged) { 762 mWifiMetrics.logUserActionEvent(enable 763 ? UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON 764 : UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF, 765 provider.isFromSuggestion(), true); 766 mWifiConfigManager.removePasspointConfiguredNetwork( 767 provider.getWifiConfig().getProfileKey()); 768 } 769 found = true; 770 } 771 } 772 if (found) { 773 mWifiConfigManager.saveToStore(true); 774 } 775 return found; 776 } 777 778 /** 779 * Set the metered override value for this passpoint profile 780 * @param fqdn The FQDN of the configuration 781 * @param meteredOverride One of the values in {@link MeteredOverride} 782 * @return true on success, false otherwise (e.g. if no such provider exists). 783 */ setMeteredOverride(@onNull String fqdn, @MeteredOverride int meteredOverride)784 public boolean setMeteredOverride(@NonNull String fqdn, @MeteredOverride int meteredOverride) { 785 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 786 boolean found = false; 787 788 // Loop through all profiles with matching FQDN 789 for (PasspointProvider provider : passpointProviders) { 790 if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 791 if (provider.setMeteredOverride(meteredOverride)) { 792 mWifiMetrics.logUserActionEvent( 793 WifiMetrics.convertMeteredOverrideEnumToUserActionEventType( 794 meteredOverride), 795 provider.isFromSuggestion(), true); 796 } 797 found = true; 798 } 799 } 800 if (found) { 801 mWifiConfigManager.saveToStore(true); 802 } 803 return found; 804 } 805 806 /** 807 * Return the installed Passpoint provider configurations. 808 * An empty list will be returned when no provider is installed. 809 * 810 * @param callingUid Calling UID. 811 * @param privileged Whether the caller is a privileged entity 812 * @return A list of {@link PasspointConfiguration} 813 */ getProviderConfigs(int callingUid, boolean privileged)814 public List<PasspointConfiguration> getProviderConfigs(int callingUid, 815 boolean privileged) { 816 List<PasspointConfiguration> configs = new ArrayList<>(); 817 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 818 PasspointProvider provider = entry.getValue(); 819 if (privileged || callingUid == provider.getCreatorUid()) { 820 if (provider.isFromSuggestion()) { 821 continue; 822 } 823 configs.add(provider.getConfig()); 824 } 825 } 826 return configs; 827 } 828 829 /** 830 * Find all providers that can provide service through the given AP, which means the 831 * providers contained credential to authenticate with the given AP. 832 * 833 * If there is any home provider available, will return a list of matched home providers. 834 * Otherwise will return a list of matched roaming providers. 835 * 836 * A empty list will be returned if no matching is found. 837 * 838 * @param scanResult The scan result associated with the AP 839 * @return a list of pairs of {@link PasspointProvider} and match status. 840 */ matchProvider( ScanResult scanResult)841 public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider( 842 ScanResult scanResult) { 843 return matchProvider(scanResult, true); 844 } 845 846 /** 847 * Find all providers that can provide service through the given AP, which means the 848 * providers contained credential to authenticate with the given AP. 849 * 850 * A empty list will be returned if no matching is found. 851 * 852 * @param scanResult The scan result associated with the AP 853 * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty 854 * @return a list of pairs of {@link PasspointProvider} and match status. 855 */ matchProvider( ScanResult scanResult, boolean anqpRequestAllowed)856 public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider( 857 ScanResult scanResult, boolean anqpRequestAllowed) { 858 if (!mEnabled) { 859 return Collections.emptyList(); 860 } 861 List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders( 862 scanResult, anqpRequestAllowed).stream() 863 .filter(a -> !isExpired(a.first.getConfig())) 864 .collect(Collectors.toList()); 865 if (allMatches.isEmpty()) { 866 if (mVerboseLoggingEnabled) { 867 Log.d(TAG, "No service provider found for " + scanResult.SSID); 868 } 869 } 870 return allMatches; 871 } 872 873 /** 874 * Return a list of all providers that can provide service through the given AP. 875 * 876 * @param scanResult The scan result associated with the AP 877 * @return a list of pairs of {@link PasspointProvider} and match status. 878 */ getAllMatchedProviders( ScanResult scanResult)879 public @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders( 880 ScanResult scanResult) { 881 return getAllMatchedProviders(scanResult, true); 882 } 883 884 /** 885 * Return a list of all providers that can provide service through the given AP. 886 * 887 * @param scanResult The scan result associated with the AP 888 * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty 889 * @return a list of pairs of {@link PasspointProvider} and match status. 890 */ getAllMatchedProviders( ScanResult scanResult, boolean anqpRequestAllowed)891 private @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders( 892 ScanResult scanResult, boolean anqpRequestAllowed) { 893 List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>(); 894 895 // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0 896 // Vendor Specific IE. 897 InformationElementUtil.RoamingConsortium roamingConsortium = 898 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements); 899 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 900 scanResult.informationElements); 901 902 // Lookup ANQP data in the cache. 903 long bssid; 904 try { 905 bssid = Utils.parseMac(scanResult.BSSID); 906 } catch (IllegalArgumentException e) { 907 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 908 return allMatches; 909 } 910 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 911 vsa.anqpDomainID); 912 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 913 if (anqpEntry == null) { 914 if (anqpRequestAllowed) { 915 mAnqpRequestManager.requestANQPElements(bssid, anqpKey, 916 roamingConsortium.anqpOICount > 0, vsa.hsRelease); 917 } 918 Log.d(TAG, "ANQP entry not found for: " + anqpKey); 919 return allMatches; 920 } 921 boolean anyProviderUpdated = false; 922 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 923 PasspointProvider provider = entry.getValue(); 924 if (provider.tryUpdateCarrierId()) { 925 anyProviderUpdated = true; 926 } 927 if (mVerboseLoggingEnabled) { 928 Log.d(TAG, "Matching provider " + provider.getConfig().getHomeSp().getFqdn() 929 + " with " 930 + anqpEntry.getElements().get(Constants.ANQPElementType.ANQPDomName)); 931 } 932 PasspointMatch matchStatus = provider.match(anqpEntry.getElements(), 933 roamingConsortium, scanResult); 934 if (matchStatus == PasspointMatch.HomeProvider 935 || matchStatus == PasspointMatch.RoamingProvider) { 936 allMatches.add(Pair.create(provider, matchStatus)); 937 } 938 } 939 if (anyProviderUpdated) { 940 mWifiConfigManager.saveToStore(true); 941 } 942 if (allMatches.size() != 0) { 943 for (Pair<PasspointProvider, PasspointMatch> match : allMatches) { 944 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID, 945 match.first.getConfig().getHomeSp().getFqdn(), 946 match.second == PasspointMatch.HomeProvider ? "Home Provider" 947 : "Roaming Provider")); 948 } 949 } else { 950 if (mVerboseLoggingEnabled) { 951 Log.d(TAG, "No service providers found for " + scanResult.SSID); 952 } 953 } 954 return allMatches; 955 } 956 957 /** 958 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the 959 * current {@link PasspointManager}. 960 * 961 * This will not trigger a config store write, since this will be invoked as part of the 962 * configuration migration, the caller will be responsible for triggering store write 963 * after the migration is completed. 964 * 965 * @param config {@link WifiConfiguration} representation of the Passpoint configuration 966 * @return true on success 967 */ addLegacyPasspointConfig(WifiConfiguration config)968 public static boolean addLegacyPasspointConfig(WifiConfiguration config) { 969 if (sPasspointManager == null) { 970 Log.e(TAG, "PasspointManager have not been initialized yet"); 971 return false; 972 } 973 Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN); 974 return sPasspointManager.addWifiConfig(config); 975 } 976 977 /** 978 * Sweep the ANQP cache to remove expired entries. 979 */ sweepCache()980 public void sweepCache() { 981 mAnqpCache.sweep(); 982 } 983 984 /** 985 * Notify the completion of an ANQP request. 986 * TODO(zqiu): currently the notification is done through WifiMonitor, 987 * will no longer be the case once we switch over to use wificond. 988 */ notifyANQPDone(AnqpEvent anqpEvent)989 public void notifyANQPDone(AnqpEvent anqpEvent) { 990 mPasspointEventHandler.notifyANQPDone(anqpEvent); 991 } 992 993 /** 994 * Notify the completion of an icon request. 995 * TODO(zqiu): currently the notification is done through WifiMonitor, 996 * will no longer be the case once we switch over to use wificond. 997 */ notifyIconDone(IconEvent iconEvent)998 public void notifyIconDone(IconEvent iconEvent) { 999 mPasspointEventHandler.notifyIconDone(iconEvent); 1000 } 1001 1002 /** 1003 * Notify the reception of a Wireless Network Management (WNM) frame. 1004 */ receivedWnmFrame(WnmData data)1005 public void receivedWnmFrame(WnmData data) { 1006 mPasspointEventHandler.notifyWnmFrameReceived(data); 1007 } 1008 1009 /** 1010 * Request the specified icon file |fileName| from the specified AP |bssid|. 1011 * @return true if the request is sent successfully, false otherwise 1012 */ queryPasspointIcon(long bssid, String fileName)1013 public boolean queryPasspointIcon(long bssid, String fileName) { 1014 return mPasspointEventHandler.requestIcon(bssid, fileName); 1015 } 1016 1017 /** 1018 * Lookup the ANQP elements associated with the given AP from the cache. An empty map 1019 * will be returned if no match found in the cache. 1020 * 1021 * @param scanResult The scan result associated with the AP 1022 * @return Map of ANQP elements 1023 */ getANQPElements(ScanResult scanResult)1024 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) { 1025 // Retrieve the Hotspot 2.0 Vendor Specific IE. 1026 InformationElementUtil.Vsa vsa = 1027 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements); 1028 1029 // Lookup ANQP data in the cache. 1030 long bssid; 1031 try { 1032 bssid = Utils.parseMac(scanResult.BSSID); 1033 } catch (IllegalArgumentException e) { 1034 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 1035 return new HashMap<>(); 1036 } 1037 ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey( 1038 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID)); 1039 if (anqpEntry != null) { 1040 return anqpEntry.getElements(); 1041 } 1042 return new HashMap<>(); 1043 } 1044 1045 /** 1046 * Return a map of all matching configurations keys with corresponding scanResults (or an empty 1047 * map if none). 1048 * 1049 * @param scanResults The list of scan results 1050 * @return Map that consists of identifies and corresponding scanResults per network type 1051 * ({@link WifiManager#PASSPOINT_HOME_NETWORK}, {@link WifiManager#PASSPOINT_ROAMING_NETWORK}). 1052 */ 1053 public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults)1054 getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults) { 1055 if (scanResults == null) { 1056 Log.e(TAG, "Attempt to get matching config for a null ScanResults"); 1057 return new HashMap<>(); 1058 } 1059 Map<String, Map<Integer, List<ScanResult>>> configs = new HashMap<>(); 1060 1061 for (ScanResult scanResult : scanResults) { 1062 if (!scanResult.isPasspointNetwork()) continue; 1063 List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders( 1064 scanResult); 1065 for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) { 1066 WifiConfiguration config = matchedProvider.first.getWifiConfig(); 1067 int type = WifiManager.PASSPOINT_HOME_NETWORK; 1068 if (!config.isHomeProviderNetwork) { 1069 type = WifiManager.PASSPOINT_ROAMING_NETWORK; 1070 } 1071 Map<Integer, List<ScanResult>> scanResultsPerNetworkType = 1072 configs.computeIfAbsent(config.getProfileKey(), 1073 k -> new HashMap<>()); 1074 List<ScanResult> matchingScanResults = scanResultsPerNetworkType.computeIfAbsent( 1075 type, k -> new ArrayList<>()); 1076 matchingScanResults.add(scanResult); 1077 } 1078 } 1079 1080 return configs; 1081 } 1082 1083 /** 1084 * Returns the list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given list 1085 * of ScanResult. 1086 * 1087 * An empty map will be returned when an invalid scanResults are provided or no match is found. 1088 * 1089 * @param scanResults a list of ScanResult that has Passpoint APs. 1090 * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult} 1091 */ getMatchingOsuProviders( List<ScanResult> scanResults)1092 public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders( 1093 List<ScanResult> scanResults) { 1094 if (scanResults == null) { 1095 Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult"); 1096 return new HashMap(); 1097 } 1098 1099 Map<OsuProvider, List<ScanResult>> osuProviders = new HashMap<>(); 1100 for (ScanResult scanResult : scanResults) { 1101 if (!scanResult.isPasspointNetwork()) continue; 1102 1103 // Lookup OSU Providers ANQP element. 1104 Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult); 1105 if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) { 1106 continue; 1107 } 1108 HSOsuProvidersElement element = 1109 (HSOsuProvidersElement) anqpElements.get( 1110 Constants.ANQPElementType.HSOSUProviders); 1111 for (OsuProviderInfo info : element.getProviders()) { 1112 // Set null for OSU-SSID in the class because OSU-SSID is a factor for hotspot 1113 // operator rather than service provider, which means it can be different for 1114 // each hotspot operators. 1115 OsuProvider provider = new OsuProvider((WifiSsid) null, info.getFriendlyNames(), 1116 info.getServiceDescription(), info.getServerUri(), 1117 info.getNetworkAccessIdentifier(), info.getMethodList()); 1118 List<ScanResult> matchingScanResults = osuProviders.get(provider); 1119 if (matchingScanResults == null) { 1120 matchingScanResults = new ArrayList<>(); 1121 osuProviders.put(provider, matchingScanResults); 1122 } 1123 matchingScanResults.add(scanResult); 1124 } 1125 } 1126 return osuProviders; 1127 } 1128 1129 /** 1130 * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) providers 1131 * 1132 * An empty map will be returned when an invalid {@code osuProviders} are provided or no match 1133 * is found. 1134 * 1135 * @param osuProviders a list of {@link OsuProvider} 1136 * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}. 1137 */ getMatchingPasspointConfigsForOsuProviders( List<OsuProvider> osuProviders)1138 public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders( 1139 List<OsuProvider> osuProviders) { 1140 Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>(); 1141 1142 for (OsuProvider osuProvider : osuProviders) { 1143 Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList(); 1144 if (friendlyNamesForOsuProvider == null) continue; 1145 for (PasspointProvider provider : mProviders.values()) { 1146 PasspointConfiguration passpointConfiguration = provider.getConfig(); 1147 Map<String, String> serviceFriendlyNamesForPpsMo = 1148 passpointConfiguration.getServiceFriendlyNames(); 1149 if (serviceFriendlyNamesForPpsMo == null) continue; 1150 1151 for (Map.Entry<String, String> entry : serviceFriendlyNamesForPpsMo.entrySet()) { 1152 String lang = entry.getKey(); 1153 String friendlyName = entry.getValue(); 1154 if (friendlyName == null) continue; 1155 String osuFriendlyName = friendlyNamesForOsuProvider.get(lang); 1156 if (osuFriendlyName == null) continue; 1157 if (friendlyName.equals(osuFriendlyName)) { 1158 matchingPasspointConfigs.put(osuProvider, passpointConfiguration); 1159 break; 1160 } 1161 } 1162 } 1163 } 1164 return matchingPasspointConfigs; 1165 } 1166 1167 /** 1168 * Returns the corresponding wifi configurations from {@link WifiConfigManager} for given a list 1169 * of Passpoint profile unique identifiers. 1170 * 1171 * Note: Not all matched Passpoint profile's WifiConfiguration will be returned, only the ones 1172 * already be added into the {@link WifiConfigManager} will be returned. As the returns of this 1173 * method is expected to show in Wifi Picker or use with 1174 * {@link WifiManager#connect(int, WifiManager.ActionListener)} API, each WifiConfiguration must 1175 * have a valid network Id. 1176 * 1177 * An empty list will be returned when no match is found. 1178 * 1179 * @param idList a list of unique identifiers 1180 * @return List of {@link WifiConfiguration} converted from {@link PasspointProvider} 1181 */ getWifiConfigsForPasspointProfiles(List<String> idList)1182 public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> idList) { 1183 if (mProviders.isEmpty()) { 1184 return Collections.emptyList(); 1185 } 1186 List<WifiConfiguration> configs = new ArrayList<>(); 1187 Set<String> uniqueIdSet = new HashSet<>(); 1188 uniqueIdSet.addAll(idList); 1189 for (String uniqueId : uniqueIdSet) { 1190 PasspointProvider provider = mProviders.get(uniqueId); 1191 if (provider == null) { 1192 continue; 1193 } 1194 WifiConfiguration config = provider.getWifiConfig(); 1195 config = mWifiConfigManager.getConfiguredNetwork(config.getProfileKey()); 1196 if (config == null) { 1197 continue; 1198 } 1199 // If the Passpoint configuration is from a suggestion, check if the app shares this 1200 // suggestion with the user. 1201 if (provider.isFromSuggestion() 1202 && !mWifiInjector.getWifiNetworkSuggestionsManager() 1203 .isPasspointSuggestionSharedWithUser(config)) { 1204 continue; 1205 } 1206 if (mWifiConfigManager.shouldUseNonPersistentRandomization(config)) { 1207 config.setRandomizedMacAddress(MacAddress.fromString(DEFAULT_MAC_ADDRESS)); 1208 } else { 1209 MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(), 1210 mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID)); 1211 if (result != null) { 1212 config.setRandomizedMacAddress(result); 1213 } 1214 } 1215 configs.add(config); 1216 } 1217 return configs; 1218 } 1219 1220 /** 1221 * Invoked when a Passpoint network was successfully connected based on the credentials 1222 * provided by the given Passpoint provider 1223 * 1224 * @param uniqueId The unique identifier of the Passpointprofile 1225 */ onPasspointNetworkConnected(String uniqueId)1226 public void onPasspointNetworkConnected(String uniqueId) { 1227 PasspointProvider provider = mProviders.get(uniqueId); 1228 if (provider == null) { 1229 Log.e(TAG, "Passpoint network connected without provider: " + uniqueId); 1230 return; 1231 } 1232 if (!provider.getHasEverConnected()) { 1233 // First successful connection using this provider. 1234 provider.setHasEverConnected(true); 1235 } 1236 } 1237 1238 /** 1239 * Update metrics related to installed Passpoint providers, this includes the number of 1240 * installed providers and the number of those providers that results in a successful network 1241 * connection. 1242 */ updateMetrics()1243 public void updateMetrics() { 1244 int numProviders = mProviders.size(); 1245 int numConnectedProviders = 0; 1246 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 1247 if (entry.getValue().getHasEverConnected()) { 1248 numConnectedProviders++; 1249 } 1250 } 1251 mWifiMetrics.updateSavedPasspointProfilesInfo(mProviders); 1252 mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders); 1253 } 1254 1255 /** 1256 * Dump the current state of PasspointManager to the provided output stream. 1257 * 1258 * @param pw The output stream to write to 1259 */ dump(PrintWriter pw)1260 public void dump(PrintWriter pw) { 1261 pw.println("Dump of PasspointManager"); 1262 pw.println("mEnabled: " + mEnabled); 1263 pw.println("PasspointManager - Providers Begin ---"); 1264 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 1265 pw.println(entry.getValue()); 1266 } 1267 pw.println("PasspointManager - Providers End ---"); 1268 pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex); 1269 mAnqpCache.dump(pw); 1270 mAnqpRequestManager.dump(pw); 1271 } 1272 1273 /** 1274 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}. 1275 * 1276 * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration 1277 * @return true on success 1278 */ addWifiConfig(WifiConfiguration wifiConfig)1279 private boolean addWifiConfig(WifiConfiguration wifiConfig) { 1280 if (wifiConfig == null) { 1281 return false; 1282 } 1283 1284 // Convert to PasspointConfiguration 1285 PasspointConfiguration passpointConfig = 1286 PasspointProvider.convertFromWifiConfig(wifiConfig); 1287 if (passpointConfig == null) { 1288 return false; 1289 } 1290 1291 // Setup aliases for enterprise certificates and key. 1292 WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig; 1293 String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias(); 1294 String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias(); 1295 if (passpointConfig.getCredential().getUserCredential() != null 1296 && TextUtils.isEmpty(caCertificateAliasSuffix)) { 1297 Log.e(TAG, "Missing CA Certificate for user credential"); 1298 return false; 1299 } 1300 if (passpointConfig.getCredential().getCertCredential() != null) { 1301 if (TextUtils.isEmpty(caCertificateAliasSuffix)) { 1302 Log.e(TAG, "Missing CA certificate for Certificate credential"); 1303 return false; 1304 } 1305 if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) { 1306 Log.e(TAG, "Missing client certificate and key for certificate credential"); 1307 return false; 1308 } 1309 } 1310 1311 // Note that for legacy configuration, the alias for client private key is the same as the 1312 // alias for the client certificate. 1313 PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore, 1314 mWifiCarrierInfoManager, 1315 mProviderIndex++, wifiConfig.creatorUid, null, false, 1316 Arrays.asList(enterpriseConfig.getCaCertificateAlias()), 1317 enterpriseConfig.getClientCertificateAlias(), null, false, false, mClock); 1318 provider.enableVerboseLogging(mVerboseLoggingEnabled); 1319 mProviders.put(passpointConfig.getUniqueId(), provider); 1320 return true; 1321 } 1322 1323 /** 1324 * Start the subscription provisioning flow with a provider. 1325 * @param callingUid integer indicating the uid of the caller 1326 * @param provider {@link OsuProvider} the provider to subscribe to 1327 * @param callback {@link IProvisioningCallback} callback to update status to the caller 1328 * @return boolean return value from the provisioning method 1329 */ startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)1330 public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider, 1331 IProvisioningCallback callback) { 1332 return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback); 1333 } 1334 1335 /** 1336 * Check if a Passpoint configuration is expired 1337 * 1338 * @param config {@link PasspointConfiguration} Passpoint configuration 1339 * @return True if the configuration is expired, false if not or expiration is unset 1340 */ isExpired(@onNull PasspointConfiguration config)1341 private boolean isExpired(@NonNull PasspointConfiguration config) { 1342 long expirationTime = config.getSubscriptionExpirationTimeMillis(); 1343 1344 if (expirationTime != Long.MIN_VALUE) { 1345 long curTime = System.currentTimeMillis(); 1346 1347 // Check expiration and return true for expired profiles 1348 if (curTime >= expirationTime) { 1349 Log.d(TAG, "Profile for " + config.getServiceFriendlyName() + " has expired, " 1350 + "expiration time: " + expirationTime + ", current time: " 1351 + curTime); 1352 return true; 1353 } 1354 } 1355 return false; 1356 } 1357 1358 /** 1359 * Get the filtered ScanResults which could be served by the {@link PasspointConfiguration}. 1360 * @param passpointConfiguration The instance of {@link PasspointConfiguration} 1361 * @param scanResults The list of {@link ScanResult} 1362 * @return The filtered ScanResults 1363 */ 1364 @NonNull getMatchingScanResults( @onNull PasspointConfiguration passpointConfiguration, @NonNull List<ScanResult> scanResults)1365 public List<ScanResult> getMatchingScanResults( 1366 @NonNull PasspointConfiguration passpointConfiguration, 1367 @NonNull List<ScanResult> scanResults) { 1368 PasspointProvider provider = mObjectFactory.makePasspointProvider(passpointConfiguration, 1369 null, mWifiCarrierInfoManager, 0, 0, null, false, mClock); 1370 List<ScanResult> filteredScanResults = new ArrayList<>(); 1371 for (ScanResult scanResult : scanResults) { 1372 PasspointMatch matchInfo = provider.match(getANQPElements(scanResult), 1373 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements), 1374 scanResult); 1375 if (matchInfo == PasspointMatch.HomeProvider 1376 || matchInfo == PasspointMatch.RoamingProvider) { 1377 filteredScanResults.add(scanResult); 1378 } 1379 } 1380 1381 return filteredScanResults; 1382 } 1383 1384 /** 1385 * Check if the providers list is empty 1386 * 1387 * @return true if the providers list is empty, false otherwise 1388 */ isProvidersListEmpty()1389 public boolean isProvidersListEmpty() { 1390 return mProviders.isEmpty(); 1391 } 1392 1393 /** 1394 * Clear ANQP requests and flush ANQP Cache (for factory reset) 1395 */ clearAnqpRequestsAndFlushCache()1396 public void clearAnqpRequestsAndFlushCache() { 1397 mAnqpRequestManager.clear(); 1398 mAnqpCache.flush(); 1399 mProviders.values().stream().forEach(p -> p.clearProviderBlock()); 1400 } 1401 1402 private PKIXParameters mInjectedPKIXParameters; 1403 private boolean mUseInjectedPKIX = false; 1404 1405 1406 /** 1407 * Used to speedup unit test. 1408 */ 1409 @VisibleForTesting injectPKIXParameters(PKIXParameters params)1410 public void injectPKIXParameters(PKIXParameters params) { 1411 mInjectedPKIXParameters = params; 1412 } 1413 1414 /** 1415 * Used to speedup unit test. 1416 */ 1417 @VisibleForTesting setUseInjectedPKIX(boolean value)1418 public void setUseInjectedPKIX(boolean value) { 1419 mUseInjectedPKIX = value; 1420 } 1421 1422 /** 1423 * Verify that the given certificate is trusted by one of the pre-loaded public CAs in the 1424 * system key store. 1425 * 1426 * @param caCert The CA Certificate to verify 1427 * @throws CertPathValidatorException 1428 * @throws Exception 1429 */ verifyCaCert(X509Certificate caCert)1430 private void verifyCaCert(X509Certificate caCert) 1431 throws GeneralSecurityException, IOException { 1432 CertificateFactory factory = CertificateFactory.getInstance("X.509"); 1433 CertPathValidator validator = 1434 CertPathValidator.getInstance(CertPathValidator.getDefaultType()); 1435 CertPath path = factory.generateCertPath(Arrays.asList(caCert)); 1436 PKIXParameters params; 1437 if (mUseInjectedPKIX) { 1438 params = mInjectedPKIXParameters; 1439 } else { 1440 KeyStore ks = KeyStore.getInstance("AndroidCAStore"); 1441 ks.load(null, null); 1442 params = new PKIXParameters(ks); 1443 params.setRevocationEnabled(false); 1444 } 1445 validator.validate(path, params); 1446 } 1447 1448 /** 1449 * Request the Venue URL ANQP-element from the AP post connection 1450 * 1451 * @param scanResult Scan result associated to the requested AP 1452 */ requestVenueUrlAnqpElement(@onNull ScanResult scanResult)1453 public void requestVenueUrlAnqpElement(@NonNull ScanResult scanResult) { 1454 long bssid; 1455 try { 1456 bssid = Utils.parseMac(scanResult.BSSID); 1457 } catch (IllegalArgumentException e) { 1458 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 1459 return; 1460 } 1461 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 1462 scanResult.informationElements); 1463 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 1464 vsa.anqpDomainID); 1465 // TODO(haishalom@): Should we limit to R3 only? vsa.hsRelease > NetworkDetail.HSRelease.R2 1466 // I am seeing R2's that respond to Venue URL request, so may keep it this way. 1467 // APs that do not support this ANQP request simply ignore it. 1468 mAnqpRequestManager.requestVenueUrlAnqpElement(bssid, anqpKey); 1469 } 1470 1471 /** 1472 * Get the Venue URL associated to the scan result, matched to the system language. If no 1473 * Venue URL matches the system language, then entry number one is returned, which is considered 1474 * to be the venue's default language. 1475 * 1476 * @param scanResult Scan result 1477 * @return The Venue URL associated to the scan result or null if not found 1478 */ 1479 @Nullable getVenueUrl(@onNull ScanResult scanResult)1480 public URL getVenueUrl(@NonNull ScanResult scanResult) { 1481 long bssid; 1482 try { 1483 bssid = Utils.parseMac(scanResult.BSSID); 1484 } catch (IllegalArgumentException e) { 1485 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 1486 return null; 1487 } 1488 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 1489 scanResult.informationElements); 1490 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 1491 vsa.anqpDomainID); 1492 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 1493 if (anqpEntry == null) { 1494 return null; 1495 } 1496 VenueUrlElement venueUrlElement = (VenueUrlElement) 1497 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueUrl); 1498 if (venueUrlElement == null || venueUrlElement.getVenueUrls().isEmpty()) { 1499 return null; // No Venue URL 1500 } 1501 VenueNameElement venueNameElement = (VenueNameElement) 1502 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueName); 1503 if (venueNameElement == null 1504 || venueUrlElement.getVenueUrls().size() != venueNameElement.getNames().size()) { 1505 Log.w(TAG, "Venue name list size mismatches the Venue URL list size"); 1506 return null; // No match between Venue names Venue URLs 1507 } 1508 1509 // Find the Venue URL that matches the system language. Venue URLs are ordered by venue 1510 // names. 1511 Locale locale = Locale.getDefault(); 1512 URL venueUrl = null; 1513 int index = 1; 1514 for (I18Name venueName : venueNameElement.getNames()) { 1515 if (venueName.getLanguage().equals(locale.getISO3Language())) { 1516 venueUrl = venueUrlElement.getVenueUrls().get(index); 1517 break; 1518 } 1519 index++; 1520 } 1521 1522 // If no venue URL for the system language is available, use entry number one 1523 if (venueUrl == null) { 1524 venueUrl = venueUrlElement.getVenueUrls().get(1); 1525 } 1526 1527 if (mVerboseLoggingEnabled) { 1528 Log.d(TAG, "Venue URL to display (language = " + locale.getDisplayLanguage() 1529 + "): " + (venueUrl != null ? venueUrl : "None")); 1530 } 1531 return venueUrl; 1532 } 1533 1534 /** 1535 * Handle Deauthentication Imminent WNM-Notification event 1536 * 1537 * @param event Deauthentication Imminent WNM-Notification data 1538 * @param config Configuration of the currently connected network 1539 */ handleDeauthImminentEvent(WnmData event, WifiConfiguration config)1540 public void handleDeauthImminentEvent(WnmData event, WifiConfiguration config) { 1541 if (event == null || config == null) { 1542 return; 1543 } 1544 1545 blockProvider(config.getProfileKey(), event.getBssid(), event.isEss(), 1546 event.getDelay()); 1547 mWifiMetrics.incrementPasspointDeauthImminentScope(event.isEss()); 1548 } 1549 1550 /** 1551 * Block a specific provider from network selection 1552 * 1553 * @param passpointUniqueId The unique ID of the Passpoint network 1554 * @param bssid BSSID of the AP 1555 * @param isEss Block the ESS or the BSS 1556 * @param delay Delay in seconds 1557 */ blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay)1558 private void blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay) { 1559 PasspointProvider provider = mProviders.get(passpointUniqueId); 1560 if (provider != null) { 1561 provider.blockBssOrEss(bssid, isEss, delay); 1562 } 1563 } 1564 1565 /** 1566 * Store the AnonymousIdentity for passpoint after connection. 1567 */ setAnonymousIdentity(WifiConfiguration configuration)1568 public void setAnonymousIdentity(WifiConfiguration configuration) { 1569 if (!configuration.isPasspoint()) { 1570 return; 1571 } 1572 PasspointProvider provider = mProviders.get(configuration.getProfileKey()); 1573 if (provider != null) { 1574 provider.setAnonymousIdentity(configuration.enterpriseConfig.getAnonymousIdentity()); 1575 mWifiConfigManager.saveToStore(true); 1576 } 1577 } 1578 1579 /** 1580 * Resets all sim networks state. 1581 */ resetSimPasspointNetwork()1582 public void resetSimPasspointNetwork() { 1583 mProviders.values().stream().forEach(p -> p.setAnonymousIdentity(null)); 1584 mWifiConfigManager.saveToStore(true); 1585 } 1586 1587 /** 1588 * Handle Terms & Conditions acceptance required WNM-Notification event 1589 * 1590 * @param event Terms & Conditions WNM-Notification data 1591 * @param config Configuration of the currently connected Passpoint network 1592 * 1593 * @return The Terms & conditions URL if it is valid, null otherwise 1594 */ handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config)1595 public URL handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config) { 1596 if (event == null || config == null || !config.isPasspoint()) { 1597 return null; 1598 } 1599 final int oneHourInSeconds = 60 * 60; 1600 final int twentyFourHoursInSeconds = 24 * 60 * 60; 1601 final URL termsAndConditionsUrl; 1602 try { 1603 termsAndConditionsUrl = new URL(event.getUrl()); 1604 } catch (java.net.MalformedURLException e) { 1605 Log.e(TAG, "Malformed Terms and Conditions URL: " + event.getUrl() 1606 + " from BSSID: " + Utils.macToString(event.getBssid())); 1607 1608 // Block this provider for an hour, this unlikely issue may be resolved shortly 1609 blockProvider(config.getProfileKey(), event.getBssid(), true, oneHourInSeconds); 1610 return null; 1611 } 1612 // Reject URLs that are not HTTPS 1613 if (!TextUtils.equals(termsAndConditionsUrl.getProtocol(), "https")) { 1614 Log.e(TAG, "Non-HTTPS Terms and Conditions URL rejected: " + termsAndConditionsUrl 1615 + " from BSSID: " + Utils.macToString(event.getBssid())); 1616 1617 // Block this provider for 24 hours, it is unlikely to be changed 1618 blockProvider(config.getProfileKey(), event.getBssid(), true, 1619 twentyFourHoursInSeconds); 1620 return null; 1621 } 1622 Log.i(TAG, "Captive network, Terms and Conditions URL: " + termsAndConditionsUrl 1623 + " from BSSID: " + Utils.macToString(event.getBssid())); 1624 return termsAndConditionsUrl; 1625 } 1626 1627 /** 1628 * Check if Wi-Fi Passpoint is enabled. 1629 * 1630 * @return true if Wi-Fi Passpoint is enabled. 1631 */ isWifiPasspointEnabled()1632 public boolean isWifiPasspointEnabled() { 1633 return mEnabled; 1634 } 1635 1636 /** 1637 * Enable or disable Wi-Fi Passpoint globally. 1638 */ setWifiPasspointEnabled(boolean enabled)1639 public void setWifiPasspointEnabled(boolean enabled) { 1640 if (enabled != mEnabled) { 1641 clearAnqpRequestsAndFlushCache(); 1642 mEnabled = enabled; 1643 mSettingsStore.handleWifiPasspointEnabled(enabled); 1644 } 1645 } 1646 1647 /** 1648 * Get the selected RCOI for a particular Passpoint network connection 1649 * @param uniqueId The Unique ID of the Passpoint configuration 1650 * @param ssid The target SSID 1651 * @return Selected RCOI for a network, or 0 if none. 1652 */ getSelectedRcoiForNetwork(String uniqueId, String ssid)1653 public long getSelectedRcoiForNetwork(String uniqueId, String ssid) { 1654 if (TextUtils.isEmpty(uniqueId) || TextUtils.isEmpty(ssid)) return 0; 1655 PasspointProvider provider = mProviders.get(uniqueId); 1656 if (provider == null) return 0; 1657 return provider.getAndRemoveMatchedRcoi(ssid); 1658 } 1659 } 1660