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