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