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.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT; 21 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON; 22 import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION; 23 import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG; 24 import static android.net.wifi.WifiManager.EXTRA_DELAY; 25 import static android.net.wifi.WifiManager.EXTRA_ESS; 26 import static android.net.wifi.WifiManager.EXTRA_FILENAME; 27 import static android.net.wifi.WifiManager.EXTRA_ICON; 28 import static android.net.wifi.WifiManager.EXTRA_SUBSCRIPTION_REMEDIATION_METHOD; 29 import static android.net.wifi.WifiManager.EXTRA_URL; 30 31 import static com.android.server.wifi.hotspot2.Utils.isCarrierEapMethod; 32 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.app.AppOpsManager; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.graphics.drawable.Icon; 39 import android.net.wifi.ScanResult; 40 import android.net.wifi.WifiConfiguration; 41 import android.net.wifi.WifiEnterpriseConfig; 42 import android.net.wifi.WifiManager; 43 import android.net.wifi.hotspot2.IProvisioningCallback; 44 import android.net.wifi.hotspot2.OsuProvider; 45 import android.net.wifi.hotspot2.PasspointConfiguration; 46 import android.net.wifi.hotspot2.pps.Credential; 47 import android.net.wifi.hotspot2.pps.HomeSp; 48 import android.os.Handler; 49 import android.os.Looper; 50 import android.os.Process; 51 import android.os.UserHandle; 52 import android.telephony.SubscriptionManager; 53 import android.telephony.TelephonyManager; 54 import android.text.TextUtils; 55 import android.util.Log; 56 import android.util.Pair; 57 58 import com.android.server.wifi.Clock; 59 import com.android.server.wifi.IMSIParameter; 60 import com.android.server.wifi.SIMAccessor; 61 import com.android.server.wifi.ScanDetail; 62 import com.android.server.wifi.WifiConfigManager; 63 import com.android.server.wifi.WifiConfigStore; 64 import com.android.server.wifi.WifiInjector; 65 import com.android.server.wifi.WifiKeyStore; 66 import com.android.server.wifi.WifiMetrics; 67 import com.android.server.wifi.WifiNative; 68 import com.android.server.wifi.hotspot2.anqp.ANQPElement; 69 import com.android.server.wifi.hotspot2.anqp.Constants; 70 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement; 71 import com.android.server.wifi.hotspot2.anqp.NAIRealmElement; 72 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo; 73 import com.android.server.wifi.util.InformationElementUtil; 74 import com.android.server.wifi.util.TelephonyUtil; 75 76 import java.io.PrintWriter; 77 import java.security.cert.X509Certificate; 78 import java.util.ArrayList; 79 import java.util.Arrays; 80 import java.util.HashMap; 81 import java.util.HashSet; 82 import java.util.List; 83 import java.util.Map; 84 import java.util.Set; 85 import java.util.stream.Collectors; 86 87 /** 88 * This class provides the APIs to manage Passpoint provider configurations. 89 * It deals with the following: 90 * - Maintaining a list of configured Passpoint providers for provider matching. 91 * - Persisting the providers configurations to store when required. 92 * - matching Passpoint providers based on the scan results 93 * - Supporting WifiManager Public API calls: 94 * > addOrUpdatePasspointConfiguration() 95 * > removePasspointConfiguration() 96 * > getPasspointConfigurations() 97 * 98 * The provider matching requires obtaining additional information from the AP (ANQP elements). 99 * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests. 100 * 101 * NOTE: These API's are not thread safe and should only be used from ClientModeImpl thread. 102 */ 103 public class PasspointManager { 104 private static final String TAG = "PasspointManager"; 105 106 /** 107 * Handle for the current {@link PasspointManager} instance. This is needed to avoid 108 * circular dependency with the WifiConfigManger, it will be used for adding the 109 * legacy Passpoint configurations. 110 * 111 * This can be eliminated once we can remove the dependency for WifiConfigManager (for 112 * triggering config store write) from this class. 113 */ 114 private static PasspointManager sPasspointManager; 115 116 private final PasspointEventHandler mPasspointEventHandler; 117 private final WifiInjector mWifiInjector; 118 private final Handler mHandler; 119 private final SIMAccessor mSimAccessor; 120 private final WifiKeyStore mKeyStore; 121 private final PasspointObjectFactory mObjectFactory; 122 123 private final Map<String, PasspointProvider> mProviders; 124 private final AnqpCache mAnqpCache; 125 private final ANQPRequestManager mAnqpRequestManager; 126 private final WifiConfigManager mWifiConfigManager; 127 private final CertificateVerifier mCertVerifier; 128 private final WifiMetrics mWifiMetrics; 129 private final PasspointProvisioner mPasspointProvisioner; 130 private final TelephonyManager mTelephonyManager; 131 private final AppOpsManager mAppOps; 132 private final SubscriptionManager mSubscriptionManager; 133 134 /** 135 * Map of package name of an app to the app ops changed listener for the app. 136 */ 137 private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>(); 138 139 // Counter used for assigning unique identifier to each provider. 140 private long mProviderIndex; 141 private boolean mVerboseLoggingEnabled = false; 142 143 private class CallbackHandler implements PasspointEventHandler.Callbacks { 144 private final Context mContext; CallbackHandler(Context context)145 CallbackHandler(Context context) { 146 mContext = context; 147 } 148 149 @Override onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements)150 public void onANQPResponse(long bssid, 151 Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 152 // Notify request manager for the completion of a request. 153 ANQPNetworkKey anqpKey = 154 mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null); 155 if (anqpElements == null || anqpKey == null) { 156 // Query failed or the request wasn't originated from us (not tracked by the 157 // request manager). Nothing to be done. 158 return; 159 } 160 161 // Add new entry to the cache. 162 mAnqpCache.addEntry(anqpKey, anqpElements); 163 } 164 165 @Override onIconResponse(long bssid, String fileName, byte[] data)166 public void onIconResponse(long bssid, String fileName, byte[] data) { 167 Intent intent = new Intent(ACTION_PASSPOINT_ICON); 168 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 169 intent.putExtra(EXTRA_BSSID_LONG, bssid); 170 intent.putExtra(EXTRA_FILENAME, fileName); 171 if (data != null) { 172 intent.putExtra(EXTRA_ICON, Icon.createWithData(data, 0, data.length)); 173 } 174 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 175 android.Manifest.permission.ACCESS_WIFI_STATE); 176 } 177 178 @Override onWnmFrameReceived(WnmData event)179 public void onWnmFrameReceived(WnmData event) { 180 // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url 181 // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url 182 Intent intent; 183 if (event.isDeauthEvent()) { 184 intent = new Intent(ACTION_PASSPOINT_DEAUTH_IMMINENT); 185 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 186 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid()); 187 intent.putExtra(EXTRA_URL, event.getUrl()); 188 intent.putExtra(EXTRA_ESS, event.isEss()); 189 intent.putExtra(EXTRA_DELAY, event.getDelay()); 190 } else { 191 intent = new Intent(ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION); 192 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 193 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid()); 194 intent.putExtra(EXTRA_SUBSCRIPTION_REMEDIATION_METHOD, event.getMethod()); 195 intent.putExtra(EXTRA_URL, event.getUrl()); 196 } 197 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 198 android.Manifest.permission.ACCESS_WIFI_STATE); 199 } 200 } 201 202 /** 203 * Data provider for the Passpoint configuration store data 204 * {@link PasspointConfigUserStoreData}. 205 */ 206 private class UserDataSourceHandler implements PasspointConfigUserStoreData.DataSource { 207 @Override getProviders()208 public List<PasspointProvider> getProviders() { 209 List<PasspointProvider> providers = new ArrayList<>(); 210 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 211 providers.add(entry.getValue()); 212 } 213 return providers; 214 } 215 216 @Override setProviders(List<PasspointProvider> providers)217 public void setProviders(List<PasspointProvider> providers) { 218 mProviders.clear(); 219 for (PasspointProvider provider : providers) { 220 provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0); 221 mProviders.put(provider.getConfig().getHomeSp().getFqdn(), provider); 222 if (provider.getPackageName() != null) { 223 startTrackingAppOpsChange(provider.getPackageName(), 224 provider.getCreatorUid()); 225 } 226 } 227 } 228 } 229 230 /** 231 * Data provider for the Passpoint configuration store data 232 * {@link PasspointConfigSharedStoreData}. 233 */ 234 private class SharedDataSourceHandler implements PasspointConfigSharedStoreData.DataSource { 235 @Override getProviderIndex()236 public long getProviderIndex() { 237 return mProviderIndex; 238 } 239 240 @Override setProviderIndex(long providerIndex)241 public void setProviderIndex(long providerIndex) { 242 mProviderIndex = providerIndex; 243 } 244 } 245 246 /** 247 * Listener for app-ops changes for apps to remove the corresponding Passpoint profiles. 248 */ 249 private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener { 250 private final String mPackageName; 251 private final int mUid; 252 AppOpsChangedListener(@onNull String packageName, int uid)253 AppOpsChangedListener(@NonNull String packageName, int uid) { 254 mPackageName = packageName; 255 mUid = uid; 256 } 257 258 @Override onOpChanged(String op, String packageName)259 public void onOpChanged(String op, String packageName) { 260 mHandler.post(() -> { 261 if (!mPackageName.equals(packageName)) return; 262 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return; 263 264 // Ensures the uid to package mapping is still correct. 265 try { 266 mAppOps.checkPackage(mUid, mPackageName); 267 } catch (SecurityException e) { 268 Log.wtf(TAG, "Invalid uid/package" + packageName); 269 return; 270 } 271 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName) 272 == AppOpsManager.MODE_IGNORED) { 273 Log.i(TAG, "User disallowed change wifi state for " + packageName); 274 275 // Removes the profiles installed by the app from database. 276 removePasspointProviderWithPackage(mPackageName); 277 } 278 }); 279 } 280 } 281 282 /** 283 * Remove all Passpoint profiles installed by the app that has been disabled or uninstalled. 284 * 285 * @param packageName Package name of the app to remove the corresponding Passpoint profiles. 286 */ removePasspointProviderWithPackage(@onNull String packageName)287 public void removePasspointProviderWithPackage(@NonNull String packageName) { 288 stopTrackingAppOpsChange(packageName); 289 for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage( 290 packageName).entrySet()) { 291 String fqdn = entry.getValue().getConfig().getHomeSp().getFqdn(); 292 removeProvider(Process.WIFI_UID /* ignored */, true, fqdn); 293 disconnectIfPasspointNetwork(fqdn); 294 } 295 } 296 getPasspointProviderWithPackage( @onNull String packageName)297 private Map<String, PasspointProvider> getPasspointProviderWithPackage( 298 @NonNull String packageName) { 299 return mProviders.entrySet().stream().filter( 300 entry -> TextUtils.equals(packageName, 301 entry.getValue().getPackageName())).collect( 302 Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); 303 } 304 startTrackingAppOpsChange(@onNull String packageName, int uid)305 private void startTrackingAppOpsChange(@NonNull String packageName, int uid) { 306 // The package is already registered. 307 if (mAppOpsChangedListenerPerApp.containsKey(packageName)) return; 308 AppOpsChangedListener appOpsChangedListener = new AppOpsChangedListener(packageName, uid); 309 mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener); 310 mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener); 311 } 312 stopTrackingAppOpsChange(@onNull String packageName)313 private void stopTrackingAppOpsChange(@NonNull String packageName) { 314 AppOpsChangedListener appOpsChangedListener = mAppOpsChangedListenerPerApp.remove( 315 packageName); 316 if (appOpsChangedListener == null) { 317 Log.i(TAG, "No app ops listener found for " + packageName); 318 return; 319 } 320 mAppOps.stopWatchingMode(appOpsChangedListener); 321 } 322 disconnectIfPasspointNetwork(String fqdn)323 private void disconnectIfPasspointNetwork(String fqdn) { 324 WifiConfiguration currentConfiguration = 325 mWifiInjector.getClientModeImpl().getCurrentWifiConfiguration(); 326 if (currentConfiguration == null) return; 327 if (currentConfiguration.isPasspoint() && TextUtils.equals(currentConfiguration.FQDN, 328 fqdn)) { 329 Log.i(TAG, "Disconnect current Passpoint network for " + fqdn 330 + "because the profile was removed"); 331 mWifiInjector.getClientModeImpl().disconnectCommand(); 332 } 333 } 334 PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, TelephonyManager telephonyManager, SubscriptionManager subscriptionManager)335 public PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, 336 WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, SIMAccessor simAccessor, 337 PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, 338 WifiConfigStore wifiConfigStore, 339 WifiMetrics wifiMetrics, 340 TelephonyManager telephonyManager, SubscriptionManager subscriptionManager) { 341 mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiNative, 342 new CallbackHandler(context)); 343 mWifiInjector = wifiInjector; 344 mHandler = handler; 345 mKeyStore = keyStore; 346 mSimAccessor = simAccessor; 347 mObjectFactory = objectFactory; 348 mProviders = new HashMap<>(); 349 mAnqpCache = objectFactory.makeAnqpCache(clock); 350 mAnqpRequestManager = objectFactory.makeANQPRequestManager(mPasspointEventHandler, clock); 351 mCertVerifier = objectFactory.makeCertificateVerifier(); 352 mWifiConfigManager = wifiConfigManager; 353 mWifiMetrics = wifiMetrics; 354 mProviderIndex = 0; 355 mTelephonyManager = telephonyManager; 356 mSubscriptionManager = subscriptionManager; 357 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigUserStoreData( 358 mKeyStore, mSimAccessor, new UserDataSourceHandler())); 359 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigSharedStoreData( 360 new SharedDataSourceHandler())); 361 mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, wifiNative, 362 this, wifiMetrics); 363 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 364 sPasspointManager = this; 365 } 366 367 /** 368 * Initializes the provisioning flow with a looper 369 */ initializeProvisioner(Looper looper)370 public void initializeProvisioner(Looper looper) { 371 mPasspointProvisioner.init(looper); 372 } 373 374 /** 375 * Enable verbose logging 376 * @param verbose more than 0 enables verbose logging 377 */ enableVerboseLogging(int verbose)378 public void enableVerboseLogging(int verbose) { 379 mVerboseLoggingEnabled = (verbose > 0) ? true : false; 380 mPasspointProvisioner.enableVerboseLogging(verbose); 381 for (PasspointProvider provider : mProviders.values()) { 382 provider.enableVerboseLogging(verbose); 383 } 384 } 385 386 /** 387 * Add or update a Passpoint provider with the given configuration. 388 * 389 * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name). 390 * In the case when there is an existing configuration with the same FQDN 391 * a provider with the new configuration will replace the existing provider. 392 * 393 * @param config Configuration of the Passpoint provider to be added 394 * @param packageName Package name of the app adding/Updating {@code config} 395 * @return true if provider is added, false otherwise 396 */ addOrUpdateProvider(PasspointConfiguration config, int uid, String packageName)397 public boolean addOrUpdateProvider(PasspointConfiguration config, int uid, String packageName) { 398 mWifiMetrics.incrementNumPasspointProviderInstallation(); 399 if (config == null) { 400 Log.e(TAG, "Configuration not provided"); 401 return false; 402 } 403 if (!config.validate()) { 404 Log.e(TAG, "Invalid configuration"); 405 return false; 406 } 407 408 // For Hotspot 2.0 Release 1, the CA Certificate must be trusted by one of the pre-loaded 409 // public CAs in the system key store on the device. Since the provisioning method 410 // for Release 1 is not standardized nor trusted, this is a reasonable restriction 411 // to improve security. The presence of UpdateIdentifier is used to differentiate 412 // between R1 and R2 configuration. 413 X509Certificate[] x509Certificates = config.getCredential().getCaCertificates(); 414 if (config.getUpdateIdentifier() == Integer.MIN_VALUE && x509Certificates != null) { 415 try { 416 for (X509Certificate certificate : x509Certificates) { 417 mCertVerifier.verifyCaCert(certificate); 418 } 419 } catch (Exception e) { 420 Log.e(TAG, "Failed to verify CA certificate: " + e.getMessage()); 421 return false; 422 } 423 } 424 425 // Create a provider and install the necessary certificates and keys. 426 PasspointProvider newProvider = mObjectFactory.makePasspointProvider( 427 config, mKeyStore, mSimAccessor, mProviderIndex++, uid, packageName); 428 429 if (!newProvider.installCertsAndKeys()) { 430 Log.e(TAG, "Failed to install certificates and keys to keystore"); 431 return false; 432 } 433 434 // Remove existing provider with the same FQDN. 435 if (mProviders.containsKey(config.getHomeSp().getFqdn())) { 436 Log.d(TAG, "Replacing configuration for " + config.getHomeSp().getFqdn()); 437 mProviders.get(config.getHomeSp().getFqdn()).uninstallCertsAndKeys(); 438 mProviders.remove(config.getHomeSp().getFqdn()); 439 } 440 newProvider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0); 441 mProviders.put(config.getHomeSp().getFqdn(), newProvider); 442 mWifiConfigManager.saveToStore(true /* forceWrite */); 443 if (newProvider.getPackageName() != null) { 444 startTrackingAppOpsChange(newProvider.getPackageName(), uid); 445 } 446 Log.d(TAG, "Added/updated Passpoint configuration: " + config.getHomeSp().getFqdn() 447 + " by " + uid); 448 mWifiMetrics.incrementNumPasspointProviderInstallSuccess(); 449 return true; 450 } 451 452 /** 453 * Finds a EAP method from a NAI realm element matched with MCC/MNC of current carrier. 454 * 455 * @param scanDetails a list of scanResults used to find a matching AP. 456 * @return a EAP method which should be one of EAP-Methods(EAP-SIM,AKA and AKA') if matching 457 * realm is found, {@code -1} otherwise. 458 */ findEapMethodFromNAIRealmMatchedWithCarrier(List<ScanDetail> scanDetails)459 public int findEapMethodFromNAIRealmMatchedWithCarrier(List<ScanDetail> scanDetails) { 460 if (!TelephonyUtil.isSimPresent(mSubscriptionManager)) { 461 return -1; 462 } 463 if (scanDetails == null || scanDetails.isEmpty()) { 464 return -1; 465 } 466 467 String mccMnc = mTelephonyManager 468 .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId()) 469 .getSimOperator(); 470 if (mccMnc == null || mccMnc.length() < IMSIParameter.MCC_MNC_LENGTH - 1) { 471 return -1; 472 } 473 474 String domain = Utils.getRealmForMccMnc(mccMnc); 475 if (domain == null) { 476 return -1; 477 } 478 for (ScanDetail scanDetail : scanDetails) { 479 if (!scanDetail.getNetworkDetail().isInterworking()) { 480 // Skip non-Passpoint APs. 481 continue; 482 } 483 484 // Lookup ANQP data in the cache. 485 long bssid; 486 ScanResult scanResult = scanDetail.getScanResult(); 487 InformationElementUtil.RoamingConsortium roamingConsortium = 488 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements); 489 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 490 scanResult.informationElements); 491 try { 492 bssid = Utils.parseMac(scanResult.BSSID); 493 } catch (IllegalArgumentException e) { 494 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 495 continue; 496 } 497 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, 498 scanResult.hessid, 499 vsa.anqpDomainID); 500 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 501 502 if (anqpEntry == null) { 503 mAnqpRequestManager.requestANQPElements(bssid, anqpKey, 504 roamingConsortium.anqpOICount > 0, 505 vsa.hsRelease == NetworkDetail.HSRelease.R2); 506 Log.d(TAG, "ANQP entry not found for: " + anqpKey); 507 continue; 508 } 509 510 // Find a matching domain that has following EAP methods(SIM/AKA/AKA') in NAI realms. 511 NAIRealmElement naiRealmElement = (NAIRealmElement) anqpEntry.getElements().get( 512 Constants.ANQPElementType.ANQPNAIRealm); 513 int eapMethod = ANQPMatcher.getCarrierEapMethodFromMatchingNAIRealm(domain, 514 naiRealmElement); 515 if (eapMethod != -1) { 516 return eapMethod; 517 } 518 } 519 return -1; 520 } 521 522 /** 523 * Creates an ephemeral {@link PasspointConfiguration} for current carrier(SIM) on the device. 524 * 525 * @param eapMethod eapMethod used to connect Passpoint Network. 526 * @return return the {@link PasspointConfiguration} if a configuration is created successfully, 527 * {@code null} otherwise. 528 */ createEphemeralPasspointConfigForCarrier(int eapMethod)529 public PasspointConfiguration createEphemeralPasspointConfigForCarrier(int eapMethod) { 530 String mccMnc = mTelephonyManager 531 .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId()) 532 .getSimOperator(); 533 if (mccMnc == null || mccMnc.length() < IMSIParameter.MCC_MNC_LENGTH - 1) { 534 Log.e(TAG, "invalid length of mccmnc"); 535 return null; 536 } 537 538 if (!isCarrierEapMethod(eapMethod)) { 539 Log.e(TAG, "invalid eapMethod type"); 540 return null; 541 } 542 543 String domain = Utils.getRealmForMccMnc(mccMnc); 544 if (domain == null) { 545 Log.e(TAG, "can't make a home domain name using " + mccMnc); 546 return null; 547 } 548 PasspointConfiguration config = new PasspointConfiguration(); 549 HomeSp homeSp = new HomeSp(); 550 homeSp.setFqdn(domain); 551 String friendlyName = mTelephonyManager 552 .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId()) 553 .getSimOperatorName(); 554 homeSp.setFriendlyName(friendlyName); 555 config.setHomeSp(homeSp); 556 557 Credential credential = new Credential(); 558 credential.setRealm(domain); 559 Credential.SimCredential simCredential = new Credential.SimCredential(); 560 561 // prefix match 562 simCredential.setImsi(mccMnc + "*"); 563 simCredential.setEapType(eapMethod); 564 credential.setSimCredential(simCredential); 565 config.setCredential(credential); 566 if (!config.validate()) { 567 Log.e(TAG, "Transient PasspointConfiguration is not a valid format: " + config); 568 return null; 569 } 570 return config; 571 } 572 573 /** 574 * Check if the {@link PasspointProvider} for a carrier exists. 575 * @param mccmnc a MCC/MNC of the carrier to find 576 * @return {@code true} if the provider already exists, {@code false} otherwise. 577 */ hasCarrierProvider(@ullable String mccmnc)578 public boolean hasCarrierProvider(@Nullable String mccmnc) { 579 String domain = Utils.getRealmForMccMnc(mccmnc); 580 if (domain == null) { 581 Log.e(TAG, "can't make a home domain name using " + mccmnc); 582 return false; 583 } 584 585 // Check if we already have this provider 586 for (Map.Entry<String, PasspointProvider> provider : mProviders.entrySet()) { 587 PasspointConfiguration installedConfig = provider.getValue().getConfig(); 588 if (installedConfig.getCredential().getSimCredential() == null) { 589 continue; 590 } 591 if (domain.equals(provider.getKey())) { 592 // We already have the provider that has same FQDN. 593 return true; 594 } 595 596 IMSIParameter imsiParameter = provider.getValue().getImsiParameter(); 597 if (imsiParameter == null) { 598 continue; 599 } 600 601 if (imsiParameter.matchesMccMnc(mccmnc)) { 602 // We already have the provider that has same IMSI. 603 return true; 604 } 605 } 606 return false; 607 } 608 609 /** 610 * Installs a {@link PasspointConfiguration} created for auto connection with EAP-SIM/AKA/AKA'. 611 * 612 * It installs the Passpoint configuration created on runtime when the (MCC/MNC) of carrier that 613 * supports encrypted IMSI is matched with one of ScanResults 614 * 615 * @param config the Passpoint Configuration to connect the AP with EAP-SIM/AKA/AKA' 616 * @return {@code true} if config is installed successfully, {@code false} otherwise. 617 */ installEphemeralPasspointConfigForCarrier(PasspointConfiguration config)618 public boolean installEphemeralPasspointConfigForCarrier(PasspointConfiguration config) { 619 if (config == null) { 620 Log.e(TAG, "PasspointConfiguration for carrier is null"); 621 return false; 622 } 623 if (!TelephonyUtil.isSimPresent(mSubscriptionManager)) { 624 Log.e(TAG, "Sim is not presented on the device"); 625 return false; 626 } 627 Credential.SimCredential simCredential = config.getCredential().getSimCredential(); 628 if (simCredential == null || simCredential.getImsi() == null) { 629 Log.e(TAG, "This is not for a carrier configuration using EAP-SIM/AKA/AKA'"); 630 return false; 631 } 632 if (!config.validate()) { 633 Log.e(TAG, 634 "It is not a valid format for Passpoint Configuration with EAP-SIM/AKA/AKA'"); 635 return false; 636 } 637 String imsi = simCredential.getImsi(); 638 if (imsi.length() < IMSIParameter.MCC_MNC_LENGTH) { 639 Log.e(TAG, "Invalid IMSI length: " + imsi.length()); 640 return false; 641 } 642 int index = imsi.indexOf("*"); 643 if (index == -1) { 644 Log.e(TAG, "missing * in imsi"); 645 return false; 646 } 647 if (hasCarrierProvider(imsi.substring(0, index))) { 648 Log.e(TAG, "It is already in the Provider list"); 649 return false; 650 } 651 652 // Create a provider and install the necessary certificates and keys. 653 PasspointProvider newProvider = mObjectFactory.makePasspointProvider( 654 config, mKeyStore, mSimAccessor, mProviderIndex++, Process.WIFI_UID, null); 655 newProvider.setEphemeral(true); 656 Log.d(TAG, "installed PasspointConfiguration for carrier : " 657 + config.getHomeSp().getFriendlyName()); 658 mProviders.put(config.getHomeSp().getFqdn(), newProvider); 659 mWifiConfigManager.saveToStore(true /* forceWrite */); 660 return true; 661 } 662 663 /** 664 * Remove a Passpoint provider identified by the given FQDN. 665 * 666 * @param callingUid Calling UID. 667 * @param privileged Whether the caller is a privileged entity 668 * @param fqdn The FQDN of the provider to remove 669 * @return true if a provider is removed, false otherwise 670 */ removeProvider(int callingUid, boolean privileged, String fqdn)671 public boolean removeProvider(int callingUid, boolean privileged, String fqdn) { 672 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 673 String packageName; 674 PasspointProvider provider = mProviders.get(fqdn); 675 if (provider == null) { 676 Log.e(TAG, "Config doesn't exist"); 677 return false; 678 } 679 if (!privileged && callingUid != provider.getCreatorUid()) { 680 Log.e(TAG, "UID " + callingUid + " cannot remove profile created by " 681 + provider.getCreatorUid()); 682 return false; 683 } 684 provider.uninstallCertsAndKeys(); 685 packageName = provider.getPackageName(); 686 mProviders.remove(fqdn); 687 mWifiConfigManager.saveToStore(true /* forceWrite */); 688 689 // Stop monitoring the package if there is no Passpoint profile installed by the package. 690 if (mAppOpsChangedListenerPerApp.containsKey(packageName) 691 && getPasspointProviderWithPackage(packageName).size() == 0) { 692 stopTrackingAppOpsChange(packageName); 693 } 694 Log.d(TAG, "Removed Passpoint configuration: " + fqdn); 695 mWifiMetrics.incrementNumPasspointProviderUninstallSuccess(); 696 return true; 697 } 698 699 /** 700 * Remove the ephemeral providers that are created temporarily for a carrier. 701 */ removeEphemeralProviders()702 public void removeEphemeralProviders() { 703 mProviders.entrySet().removeIf(entry -> { 704 PasspointProvider provider = entry.getValue(); 705 if (provider != null && provider.isEphemeral()) { 706 mWifiConfigManager.removePasspointConfiguredNetwork(entry.getKey()); 707 return true; 708 } 709 return false; 710 }); 711 } 712 713 /** 714 * Return the installed Passpoint provider configurations. 715 * 716 * An empty list will be returned when no provider is installed. 717 * 718 * @param callingUid Calling UID. 719 * @param privileged Whether the caller is a privileged entity 720 * @return A list of {@link PasspointConfiguration} 721 */ getProviderConfigs(int callingUid, boolean privileged)722 public List<PasspointConfiguration> getProviderConfigs(int callingUid, boolean privileged) { 723 List<PasspointConfiguration> configs = new ArrayList<>(); 724 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 725 PasspointProvider provider = entry.getValue(); 726 if (privileged || callingUid == provider.getCreatorUid()) { 727 configs.add(provider.getConfig()); 728 } 729 } 730 return configs; 731 } 732 733 /** 734 * Find the best provider that can provide service through the given AP, which means the 735 * provider contained credential to authenticate with the given AP. 736 * 737 * Here is the current precedence of the matching rule in descending order: 738 * 1. Home Provider 739 * 2. Roaming Provider 740 * 741 * A {code null} will be returned if no matching is found. 742 * 743 * @param scanResult The scan result associated with the AP 744 * @return A pair of {@link PasspointProvider} and match status. 745 */ matchProvider(ScanResult scanResult)746 public Pair<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) { 747 List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders( 748 scanResult); 749 if (allMatches == null) { 750 return null; 751 } 752 Pair<PasspointProvider, PasspointMatch> bestMatch = null; 753 for (Pair<PasspointProvider, PasspointMatch> match : allMatches) { 754 if (match.second == PasspointMatch.HomeProvider) { 755 bestMatch = match; 756 break; 757 } 758 if (match.second == PasspointMatch.RoamingProvider && bestMatch == null) { 759 bestMatch = match; 760 } 761 } 762 if (bestMatch != null) { 763 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID, 764 bestMatch.first.getConfig().getHomeSp().getFqdn(), 765 bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider" 766 : "Roaming Provider")); 767 } else { 768 if (mVerboseLoggingEnabled) { 769 Log.d(TAG, "No service provider found for " + scanResult.SSID); 770 } 771 } 772 return bestMatch; 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 List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders( 782 ScanResult scanResult) { 783 List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>(); 784 785 // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0 786 // Vendor Specific IE. 787 InformationElementUtil.RoamingConsortium roamingConsortium = 788 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements); 789 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 790 scanResult.informationElements); 791 792 // Lookup ANQP data in the cache. 793 long bssid; 794 try { 795 bssid = Utils.parseMac(scanResult.BSSID); 796 } catch (IllegalArgumentException e) { 797 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 798 return allMatches; 799 } 800 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 801 vsa.anqpDomainID); 802 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 803 if (anqpEntry == null) { 804 mAnqpRequestManager.requestANQPElements(bssid, anqpKey, 805 roamingConsortium.anqpOICount > 0, 806 vsa.hsRelease == NetworkDetail.HSRelease.R2); 807 Log.d(TAG, "ANQP entry not found for: " + anqpKey); 808 return allMatches; 809 } 810 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 811 PasspointProvider provider = entry.getValue(); 812 PasspointMatch matchStatus = provider.match(anqpEntry.getElements(), 813 roamingConsortium); 814 if (matchStatus == PasspointMatch.HomeProvider 815 || matchStatus == PasspointMatch.RoamingProvider) { 816 allMatches.add(Pair.create(provider, matchStatus)); 817 } 818 } 819 if (allMatches.size() != 0) { 820 for (Pair<PasspointProvider, PasspointMatch> match : allMatches) { 821 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID, 822 match.first.getConfig().getHomeSp().getFqdn(), 823 match.second == PasspointMatch.HomeProvider ? "Home Provider" 824 : "Roaming Provider")); 825 } 826 } else { 827 if (mVerboseLoggingEnabled) { 828 Log.d(TAG, "No service providers found for " + scanResult.SSID); 829 } 830 } 831 return allMatches; 832 } 833 834 /** 835 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the 836 * current {@link PasspointManager}. 837 * 838 * This will not trigger a config store write, since this will be invoked as part of the 839 * configuration migration, the caller will be responsible for triggering store write 840 * after the migration is completed. 841 * 842 * @param config {@link WifiConfiguration} representation of the Passpoint configuration 843 * @return true on success 844 */ addLegacyPasspointConfig(WifiConfiguration config)845 public static boolean addLegacyPasspointConfig(WifiConfiguration config) { 846 if (sPasspointManager == null) { 847 Log.e(TAG, "PasspointManager have not been initialized yet"); 848 return false; 849 } 850 Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN); 851 return sPasspointManager.addWifiConfig(config); 852 } 853 854 /** 855 * Sweep the ANQP cache to remove expired entries. 856 */ sweepCache()857 public void sweepCache() { 858 mAnqpCache.sweep(); 859 } 860 861 /** 862 * Notify the completion of an ANQP request. 863 * TODO(zqiu): currently the notification is done through WifiMonitor, 864 * will no longer be the case once we switch over to use wificond. 865 */ notifyANQPDone(AnqpEvent anqpEvent)866 public void notifyANQPDone(AnqpEvent anqpEvent) { 867 mPasspointEventHandler.notifyANQPDone(anqpEvent); 868 } 869 870 /** 871 * Notify the completion of an icon request. 872 * TODO(zqiu): currently the notification is done through WifiMonitor, 873 * will no longer be the case once we switch over to use wificond. 874 */ notifyIconDone(IconEvent iconEvent)875 public void notifyIconDone(IconEvent iconEvent) { 876 mPasspointEventHandler.notifyIconDone(iconEvent); 877 } 878 879 /** 880 * Notify the reception of a Wireless Network Management (WNM) frame. 881 * TODO(zqiu): currently the notification is done through WifiMonitor, 882 * will no longer be the case once we switch over to use wificond. 883 */ receivedWnmFrame(WnmData data)884 public void receivedWnmFrame(WnmData data) { 885 mPasspointEventHandler.notifyWnmFrameReceived(data); 886 } 887 888 /** 889 * Request the specified icon file |fileName| from the specified AP |bssid|. 890 * @return true if the request is sent successfully, false otherwise 891 */ queryPasspointIcon(long bssid, String fileName)892 public boolean queryPasspointIcon(long bssid, String fileName) { 893 return mPasspointEventHandler.requestIcon(bssid, fileName); 894 } 895 896 /** 897 * Lookup the ANQP elements associated with the given AP from the cache. An empty map 898 * will be returned if no match found in the cache. 899 * 900 * @param scanResult The scan result associated with the AP 901 * @return Map of ANQP elements 902 */ getANQPElements(ScanResult scanResult)903 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) { 904 // Retrieve the Hotspot 2.0 Vendor Specific IE. 905 InformationElementUtil.Vsa vsa = 906 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements); 907 908 // Lookup ANQP data in the cache. 909 long bssid; 910 try { 911 bssid = Utils.parseMac(scanResult.BSSID); 912 } catch (IllegalArgumentException e) { 913 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 914 return new HashMap<>(); 915 } 916 ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey( 917 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID)); 918 if (anqpEntry != null) { 919 return anqpEntry.getElements(); 920 } 921 return new HashMap<>(); 922 } 923 924 /** 925 * Returns a list of FQDN (Fully Qualified Domain Name) for installed Passpoint configurations. 926 * 927 * Return the map of all matching configurations with corresponding scanResults (or an empty 928 * map if none). 929 * 930 * @param scanResults The list of scan results 931 * @return Map that consists of FQDN (Fully Qualified Domain Name) and corresponding 932 * scanResults per network type({@link WifiManager#PASSPOINT_HOME_NETWORK} and {@link 933 * WifiManager#PASSPOINT_ROAMING_NETWORK}). 934 */ getAllMatchingFqdnsForScanResults( List<ScanResult> scanResults)935 public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults( 936 List<ScanResult> scanResults) { 937 if (scanResults == null) { 938 Log.e(TAG, "Attempt to get matching config for a null ScanResults"); 939 return new HashMap<>(); 940 } 941 Map<String, Map<Integer, List<ScanResult>>> configs = new HashMap<>(); 942 943 for (ScanResult scanResult : scanResults) { 944 if (!scanResult.isPasspointNetwork()) continue; 945 List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders( 946 scanResult); 947 for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) { 948 WifiConfiguration config = matchedProvider.first.getWifiConfig(); 949 int type = WifiManager.PASSPOINT_HOME_NETWORK; 950 if (!config.isHomeProviderNetwork) { 951 type = WifiManager.PASSPOINT_ROAMING_NETWORK; 952 } 953 Map<Integer, List<ScanResult>> scanResultsPerNetworkType = configs.get(config.FQDN); 954 if (scanResultsPerNetworkType == null) { 955 scanResultsPerNetworkType = new HashMap<>(); 956 configs.put(config.FQDN, scanResultsPerNetworkType); 957 } 958 List<ScanResult> matchingScanResults = scanResultsPerNetworkType.get(type); 959 if (matchingScanResults == null) { 960 matchingScanResults = new ArrayList<>(); 961 scanResultsPerNetworkType.put(type, matchingScanResults); 962 } 963 matchingScanResults.add(scanResult); 964 } 965 } 966 967 return configs; 968 } 969 970 /** 971 * Returns the list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given list 972 * of ScanResult. 973 * 974 * An empty map will be returned when an invalid scanResults are provided or no match is found. 975 * 976 * @param scanResults a list of ScanResult that has Passpoint APs. 977 * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult} 978 */ getMatchingOsuProviders( List<ScanResult> scanResults)979 public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders( 980 List<ScanResult> scanResults) { 981 if (scanResults == null) { 982 Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult"); 983 return new HashMap(); 984 } 985 986 Map<OsuProvider, List<ScanResult>> osuProviders = new HashMap<>(); 987 for (ScanResult scanResult : scanResults) { 988 if (!scanResult.isPasspointNetwork()) continue; 989 990 // Lookup OSU Providers ANQP element. 991 Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult); 992 if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) { 993 continue; 994 } 995 HSOsuProvidersElement element = 996 (HSOsuProvidersElement) anqpElements.get( 997 Constants.ANQPElementType.HSOSUProviders); 998 for (OsuProviderInfo info : element.getProviders()) { 999 // Set null for OSU-SSID in the class because OSU-SSID is a factor for hotspot 1000 // operator rather than service provider, which means it can be different for 1001 // each hotspot operators. 1002 OsuProvider provider = new OsuProvider(null, info.getFriendlyNames(), 1003 info.getServiceDescription(), info.getServerUri(), 1004 info.getNetworkAccessIdentifier(), info.getMethodList(), null); 1005 List<ScanResult> matchingScanResults = osuProviders.get(provider); 1006 if (matchingScanResults == null) { 1007 matchingScanResults = new ArrayList<>(); 1008 osuProviders.put(provider, matchingScanResults); 1009 } 1010 matchingScanResults.add(scanResult); 1011 } 1012 } 1013 return osuProviders; 1014 } 1015 1016 /** 1017 * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) providers 1018 * 1019 * An empty map will be returned when an invalid {@code osuProviders} are provided or no match 1020 * is found. 1021 * 1022 * @param osuProviders a list of {@link OsuProvider} 1023 * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}. 1024 */ getMatchingPasspointConfigsForOsuProviders( List<OsuProvider> osuProviders)1025 public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders( 1026 List<OsuProvider> osuProviders) { 1027 Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>(); 1028 List<PasspointConfiguration> passpointConfigurations = 1029 getProviderConfigs(Process.WIFI_UID /* ignored */, true); 1030 1031 for (OsuProvider osuProvider : osuProviders) { 1032 Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList(); 1033 if (friendlyNamesForOsuProvider == null) continue; 1034 for (PasspointConfiguration passpointConfiguration : passpointConfigurations) { 1035 Map<String, String> serviceFriendlyNamesForPpsMo = 1036 passpointConfiguration.getServiceFriendlyNames(); 1037 if (serviceFriendlyNamesForPpsMo == null) continue; 1038 1039 for (Map.Entry<String, String> entry : serviceFriendlyNamesForPpsMo.entrySet()) { 1040 String lang = entry.getKey(); 1041 String friendlyName = entry.getValue(); 1042 if (friendlyName == null) continue; 1043 String osuFriendlyName = friendlyNamesForOsuProvider.get(lang); 1044 if (osuFriendlyName == null) continue; 1045 if (friendlyName.equals(osuFriendlyName)) { 1046 matchingPasspointConfigs.put(osuProvider, passpointConfiguration); 1047 break; 1048 } 1049 } 1050 } 1051 } 1052 return matchingPasspointConfigs; 1053 } 1054 1055 /** 1056 * Returns the corresponding wifi configurations for given FQDN (Fully Qualified Domain Name) 1057 * list. 1058 * 1059 * An empty list will be returned when no match is found. 1060 * 1061 * @param fqdnList a list of FQDN 1062 * @return List of {@link WifiConfiguration} converted from {@link PasspointProvider} 1063 */ getWifiConfigsForPasspointProfiles(List<String> fqdnList)1064 public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> fqdnList) { 1065 Set<String> fqdnSet = new HashSet<>(); 1066 fqdnSet.addAll(fqdnList); 1067 List<WifiConfiguration> configs = new ArrayList<>(); 1068 for (String fqdn : fqdnSet) { 1069 PasspointProvider provider = mProviders.get(fqdn); 1070 if (provider != null) { 1071 configs.add(provider.getWifiConfig()); 1072 } 1073 } 1074 return configs; 1075 } 1076 1077 /** 1078 * Invoked when a Passpoint network was successfully connected based on the credentials 1079 * provided by the given Passpoint provider (specified by its FQDN). 1080 * 1081 * @param fqdn The FQDN of the Passpoint provider 1082 */ onPasspointNetworkConnected(String fqdn)1083 public void onPasspointNetworkConnected(String fqdn) { 1084 PasspointProvider provider = mProviders.get(fqdn); 1085 if (provider == null) { 1086 Log.e(TAG, "Passpoint network connected without provider: " + fqdn); 1087 return; 1088 } 1089 if (!provider.getHasEverConnected()) { 1090 // First successful connection using this provider. 1091 provider.setHasEverConnected(true); 1092 } 1093 } 1094 1095 /** 1096 * Update metrics related to installed Passpoint providers, this includes the number of 1097 * installed providers and the number of those providers that results in a successful network 1098 * connection. 1099 */ updateMetrics()1100 public void updateMetrics() { 1101 int numProviders = mProviders.size(); 1102 int numConnectedProviders = 0; 1103 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 1104 if (entry.getValue().getHasEverConnected()) { 1105 numConnectedProviders++; 1106 } 1107 } 1108 mWifiMetrics.updateSavedPasspointProfilesInfo(mProviders); 1109 mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders); 1110 } 1111 1112 /** 1113 * Dump the current state of PasspointManager to the provided output stream. 1114 * 1115 * @param pw The output stream to write to 1116 */ dump(PrintWriter pw)1117 public void dump(PrintWriter pw) { 1118 pw.println("Dump of PasspointManager"); 1119 pw.println("PasspointManager - Providers Begin ---"); 1120 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 1121 pw.println(entry.getValue()); 1122 } 1123 pw.println("PasspointManager - Providers End ---"); 1124 pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex); 1125 mAnqpCache.dump(pw); 1126 } 1127 1128 /** 1129 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}. 1130 * 1131 * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration 1132 * @return true on success 1133 */ addWifiConfig(WifiConfiguration wifiConfig)1134 private boolean addWifiConfig(WifiConfiguration wifiConfig) { 1135 if (wifiConfig == null) { 1136 return false; 1137 } 1138 1139 // Convert to PasspointConfiguration 1140 PasspointConfiguration passpointConfig = 1141 PasspointProvider.convertFromWifiConfig(wifiConfig); 1142 if (passpointConfig == null) { 1143 return false; 1144 } 1145 1146 // Setup aliases for enterprise certificates and key. 1147 WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig; 1148 String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias(); 1149 String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias(); 1150 if (passpointConfig.getCredential().getUserCredential() != null 1151 && TextUtils.isEmpty(caCertificateAliasSuffix)) { 1152 Log.e(TAG, "Missing CA Certificate for user credential"); 1153 return false; 1154 } 1155 if (passpointConfig.getCredential().getCertCredential() != null) { 1156 if (TextUtils.isEmpty(caCertificateAliasSuffix)) { 1157 Log.e(TAG, "Missing CA certificate for Certificate credential"); 1158 return false; 1159 } 1160 if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) { 1161 Log.e(TAG, "Missing client certificate and key for certificate credential"); 1162 return false; 1163 } 1164 } 1165 1166 // Note that for legacy configuration, the alias for client private key is the same as the 1167 // alias for the client certificate. 1168 PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore, 1169 mSimAccessor, mProviderIndex++, wifiConfig.creatorUid, null, 1170 Arrays.asList(enterpriseConfig.getCaCertificateAlias()), 1171 enterpriseConfig.getClientCertificateAlias(), 1172 enterpriseConfig.getClientCertificateAlias(), null, false, false); 1173 provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0); 1174 mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider); 1175 return true; 1176 } 1177 1178 /** 1179 * Start the subscription provisioning flow with a provider. 1180 * @param callingUid integer indicating the uid of the caller 1181 * @param provider {@link OsuProvider} the provider to subscribe to 1182 * @param callback {@link IProvisioningCallback} callback to update status to the caller 1183 * @return boolean return value from the provisioning method 1184 */ startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)1185 public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider, 1186 IProvisioningCallback callback) { 1187 return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback); 1188 } 1189 } 1190