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