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