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