• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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