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