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