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