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.WifiConfiguration.MeteredOverride;
21 
22 import static java.security.cert.PKIXReason.NO_TRUST_ANCHOR;
23 
24 import android.annotation.NonNull;
25 import android.app.AppOpsManager;
26 import android.content.Context;
27 import android.net.wifi.ScanResult;
28 import android.net.wifi.WifiConfiguration;
29 import android.net.wifi.WifiEnterpriseConfig;
30 import android.net.wifi.WifiManager;
31 import android.net.wifi.WifiSsid;
32 import android.net.wifi.hotspot2.IProvisioningCallback;
33 import android.net.wifi.hotspot2.OsuProvider;
34 import android.net.wifi.hotspot2.PasspointConfiguration;
35 import android.os.Handler;
36 import android.os.Looper;
37 import android.os.Process;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.util.Pair;
41 
42 import com.android.server.wifi.Clock;
43 import com.android.server.wifi.NetworkUpdateResult;
44 import com.android.server.wifi.WifiCarrierInfoManager;
45 import com.android.server.wifi.WifiConfigManager;
46 import com.android.server.wifi.WifiConfigStore;
47 import com.android.server.wifi.WifiInjector;
48 import com.android.server.wifi.WifiKeyStore;
49 import com.android.server.wifi.WifiMetrics;
50 import com.android.server.wifi.WifiNative;
51 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
52 import com.android.server.wifi.hotspot2.anqp.Constants;
53 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
54 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
55 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
56 import com.android.server.wifi.util.InformationElementUtil;
57 
58 import java.io.IOException;
59 import java.io.PrintWriter;
60 import java.security.GeneralSecurityException;
61 import java.security.KeyStore;
62 import java.security.cert.CertPath;
63 import java.security.cert.CertPathValidator;
64 import java.security.cert.CertPathValidatorException;
65 import java.security.cert.CertificateFactory;
66 import java.security.cert.PKIXParameters;
67 import java.security.cert.X509Certificate;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collections;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.List;
74 import java.util.Map;
75 import java.util.Set;
76 import java.util.stream.Collectors;
77 
78 /**
79  * This class provides the APIs to manage Passpoint provider configurations.
80  * It deals with the following:
81  * - Maintaining a list of configured Passpoint providers for provider matching.
82  * - Persisting the providers configurations to store when required.
83  * - matching Passpoint providers based on the scan results
84  * - Supporting WifiManager Public API calls:
85  *   > addOrUpdatePasspointConfiguration()
86  *   > removePasspointConfiguration()
87  *   > getPasspointConfigurations()
88  *
89  * The provider matching requires obtaining additional information from the AP (ANQP elements).
90  * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests.
91  *
92  * NOTE: These API's are not thread safe and should only be used from the main Wifi thread.
93  */
94 public class PasspointManager {
95     private static final String TAG = "PasspointManager";
96 
97     /**
98      * Handle for the current {@link PasspointManager} instance.  This is needed to avoid
99      * circular dependency with the WifiConfigManger, it will be used for adding the
100      * legacy Passpoint configurations.
101      *
102      * This can be eliminated once we can remove the dependency for WifiConfigManager (for
103      * triggering config store write) from this class.
104      */
105     private static PasspointManager sPasspointManager;
106 
107     private final PasspointEventHandler mPasspointEventHandler;
108     private final WifiInjector mWifiInjector;
109     private final Handler mHandler;
110     private final WifiKeyStore mKeyStore;
111     private final PasspointObjectFactory mObjectFactory;
112 
113     private final Map<String, PasspointProvider> mProviders;
114     private final AnqpCache mAnqpCache;
115     private final ANQPRequestManager mAnqpRequestManager;
116     private final WifiConfigManager mWifiConfigManager;
117     private final WifiMetrics mWifiMetrics;
118     private final PasspointProvisioner mPasspointProvisioner;
119     private final AppOpsManager mAppOps;
120     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
121 
122     /**
123      * Map of package name of an app to the app ops changed listener for the app.
124      */
125     private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>();
126 
127     // Counter used for assigning unique identifier to each provider.
128     private long mProviderIndex;
129     private boolean mVerboseLoggingEnabled = false;
130 
131     private class CallbackHandler implements PasspointEventHandler.Callbacks {
132         private final Context mContext;
CallbackHandler(Context context)133         CallbackHandler(Context context) {
134             mContext = context;
135         }
136 
137         @Override
onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements)138         public void onANQPResponse(long bssid,
139                 Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
140             if (mVerboseLoggingEnabled) {
141                 Log.d(TAG, "ANQP response received from BSSID "
142                         + Utils.macToString(bssid));
143             }
144             // Notify request manager for the completion of a request.
145             ANQPNetworkKey anqpKey =
146                     mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null);
147             if (anqpElements == null || anqpKey == null) {
148                 // Query failed or the request wasn't originated from us (not tracked by the
149                 // request manager). Nothing to be done.
150                 return;
151             }
152 
153             // Add new entry to the cache.
154             mAnqpCache.addEntry(anqpKey, anqpElements);
155         }
156 
157         @Override
onIconResponse(long bssid, String fileName, byte[] data)158         public void onIconResponse(long bssid, String fileName, byte[] data) {
159             // Empty
160         }
161 
162         @Override
onWnmFrameReceived(WnmData event)163         public void onWnmFrameReceived(WnmData event) {
164             // Empty
165         }
166     }
167 
168     /**
169      * Data provider for the Passpoint configuration store data
170      * {@link PasspointConfigUserStoreData}.
171      */
172     private class UserDataSourceHandler implements PasspointConfigUserStoreData.DataSource {
173         @Override
getProviders()174         public List<PasspointProvider> getProviders() {
175             List<PasspointProvider> providers = new ArrayList<>();
176             for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
177                 providers.add(entry.getValue());
178             }
179             return providers;
180         }
181 
182         @Override
setProviders(List<PasspointProvider> providers)183         public void setProviders(List<PasspointProvider> providers) {
184             mProviders.clear();
185             for (PasspointProvider provider : providers) {
186                 provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
187                 mProviders.put(provider.getConfig().getUniqueId(), provider);
188                 if (provider.getPackageName() != null) {
189                     startTrackingAppOpsChange(provider.getPackageName(),
190                             provider.getCreatorUid());
191                 }
192             }
193         }
194     }
195 
196     /**
197      * Data provider for the Passpoint configuration store data
198      * {@link PasspointConfigSharedStoreData}.
199      */
200     private class SharedDataSourceHandler implements PasspointConfigSharedStoreData.DataSource {
201         @Override
getProviderIndex()202         public long getProviderIndex() {
203             return mProviderIndex;
204         }
205 
206         @Override
setProviderIndex(long providerIndex)207         public void setProviderIndex(long providerIndex) {
208             mProviderIndex = providerIndex;
209         }
210     }
211 
212     /**
213      * Listener for app-ops changes for apps to remove the corresponding Passpoint profiles.
214      */
215     private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener {
216         private final String mPackageName;
217         private final int mUid;
218 
AppOpsChangedListener(@onNull String packageName, int uid)219         AppOpsChangedListener(@NonNull String packageName, int uid) {
220             mPackageName = packageName;
221             mUid = uid;
222         }
223 
224         @Override
onOpChanged(String op, String packageName)225         public void onOpChanged(String op, String packageName) {
226             mHandler.post(() -> {
227                 if (!mPackageName.equals(packageName)) return;
228                 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return;
229 
230                 // Ensures the uid to package mapping is still correct.
231                 try {
232                     mAppOps.checkPackage(mUid, mPackageName);
233                 } catch (SecurityException e) {
234                     Log.wtf(TAG, "Invalid uid/package" + packageName);
235                     return;
236                 }
237                 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName)
238                         == AppOpsManager.MODE_IGNORED) {
239                     Log.i(TAG, "User disallowed change wifi state for " + packageName);
240 
241                     // Removes the profiles installed by the app from database.
242                     removePasspointProviderWithPackage(mPackageName);
243                 }
244             });
245         }
246     }
247 
248     /**
249      * Remove all Passpoint profiles installed by the app that has been disabled or uninstalled.
250      *
251      * @param packageName Package name of the app to remove the corresponding Passpoint profiles.
252      */
removePasspointProviderWithPackage(@onNull String packageName)253     public void removePasspointProviderWithPackage(@NonNull String packageName) {
254         stopTrackingAppOpsChange(packageName);
255         for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage(
256                 packageName).entrySet()) {
257             String uniqueId = entry.getValue().getConfig().getUniqueId();
258             removeProvider(Process.WIFI_UID /* ignored */, true, uniqueId, null);
259             disconnectIfPasspointNetwork(uniqueId);
260         }
261     }
262 
getPasspointProviderWithPackage( @onNull String packageName)263     private Map<String, PasspointProvider> getPasspointProviderWithPackage(
264             @NonNull String packageName) {
265         return mProviders.entrySet().stream().filter(
266                 entry -> TextUtils.equals(packageName,
267                         entry.getValue().getPackageName())).collect(
268                 Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
269     }
270 
startTrackingAppOpsChange(@onNull String packageName, int uid)271     private void startTrackingAppOpsChange(@NonNull String packageName, int uid) {
272         // The package is already registered.
273         if (mAppOpsChangedListenerPerApp.containsKey(packageName)) return;
274         AppOpsChangedListener appOpsChangedListener = new AppOpsChangedListener(packageName, uid);
275         mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener);
276         mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener);
277     }
278 
stopTrackingAppOpsChange(@onNull String packageName)279     private void stopTrackingAppOpsChange(@NonNull String packageName) {
280         AppOpsChangedListener appOpsChangedListener = mAppOpsChangedListenerPerApp.remove(
281                 packageName);
282         if (appOpsChangedListener == null) {
283             Log.i(TAG, "No app ops listener found for " + packageName);
284             return;
285         }
286         mAppOps.stopWatchingMode(appOpsChangedListener);
287     }
288 
disconnectIfPasspointNetwork(String uniqueId)289     private void disconnectIfPasspointNetwork(String uniqueId) {
290         WifiConfiguration currentConfiguration =
291                 mWifiInjector.getClientModeImpl().getCurrentWifiConfiguration();
292         if (currentConfiguration == null) return;
293         if (currentConfiguration.isPasspoint() && TextUtils.equals(currentConfiguration.getKey(),
294                 uniqueId)) {
295             Log.i(TAG, "Disconnect current Passpoint network for FQDN: "
296                     + currentConfiguration.FQDN + " and ID: " + uniqueId
297                     + " because the profile was removed");
298             mWifiInjector.getClientModeImpl().disconnectCommand();
299         }
300     }
301 
PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager)302     public PasspointManager(Context context, WifiInjector wifiInjector, Handler handler,
303             WifiNative wifiNative, WifiKeyStore keyStore, Clock clock,
304             PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager,
305             WifiConfigStore wifiConfigStore,
306             WifiMetrics wifiMetrics,
307             WifiCarrierInfoManager wifiCarrierInfoManager) {
308         mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiNative,
309                 new CallbackHandler(context));
310         mWifiInjector = wifiInjector;
311         mHandler = handler;
312         mKeyStore = keyStore;
313         mObjectFactory = objectFactory;
314         mProviders = new HashMap<>();
315         mAnqpCache = objectFactory.makeAnqpCache(clock);
316         mAnqpRequestManager = objectFactory.makeANQPRequestManager(mPasspointEventHandler, clock);
317         mWifiConfigManager = wifiConfigManager;
318         mWifiMetrics = wifiMetrics;
319         mProviderIndex = 0;
320         mWifiCarrierInfoManager = wifiCarrierInfoManager;
321         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigUserStoreData(
322                 mKeyStore, mWifiCarrierInfoManager, new UserDataSourceHandler()));
323         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigSharedStoreData(
324                 new SharedDataSourceHandler()));
325         mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, wifiNative,
326                 this, wifiMetrics);
327         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
328         sPasspointManager = this;
329     }
330 
331     /**
332      * Initializes the provisioning flow with a looper.
333      * This looper should be tied to a background worker thread since PasspointProvisioner has a
334      * heavy workload.
335      */
initializeProvisioner(Looper looper)336     public void initializeProvisioner(Looper looper) {
337         mPasspointProvisioner.init(looper);
338     }
339 
340     /**
341      * Enable verbose logging
342      * @param verbose more than 0 enables verbose logging
343      */
enableVerboseLogging(int verbose)344     public void enableVerboseLogging(int verbose) {
345         mVerboseLoggingEnabled = (verbose > 0) ? true : false;
346         mPasspointProvisioner.enableVerboseLogging(verbose);
347         for (PasspointProvider provider : mProviders.values()) {
348             provider.enableVerboseLogging(verbose);
349         }
350     }
351 
updateWifiConfigInWcmIfPresent( WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion)352     private void updateWifiConfigInWcmIfPresent(
353             WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion) {
354         WifiConfiguration configInWcm =
355                 mWifiConfigManager.getConfiguredNetwork(newConfig.getKey());
356         if (configInWcm == null) return;
357         // suggestion != saved
358         if (isFromSuggestion != configInWcm.fromWifiNetworkSuggestion) return;
359         // is suggestion from same app.
360         if (isFromSuggestion
361                 && (configInWcm.creatorUid != uid
362                 || !TextUtils.equals(configInWcm.creatorName, packageName))) {
363             return;
364         }
365         NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
366                 newConfig, uid, packageName);
367         if (!result.isSuccess()) {
368             Log.e(TAG, "Failed to update config in WifiConfigManager");
369         } else {
370             mWifiConfigManager.allowAutojoin(result.getNetworkId(), newConfig.allowAutojoin);
371             if (mVerboseLoggingEnabled) {
372                 Log.v(TAG, "Updated config in WifiConfigManager");
373             }
374         }
375     }
376 
377     /**
378      * Add or update a Passpoint provider with the given configuration.
379      *
380      * Each provider is uniquely identified by its unique identifier, see
381      * {@link PasspointConfiguration#getUniqueId()}.
382      * In the case when there is an existing configuration with the same unique identifier,
383      * a provider with the new configuration will replace the existing provider.
384      *
385      * @param config Configuration of the Passpoint provider to be added
386      * @param uid Uid of the app adding/Updating {@code config}
387      * @param packageName Package name of the app adding/Updating {@code config}
388      * @param isFromSuggestion Whether this {@code config} is from suggestion API
389      * @param isTrusted Whether this {@code config} an trusted network, default should be true.
390      *                  Only able set to false when {@code isFromSuggestion} is true, otherwise
391      *                  adding {@code config} will be false.
392      * @return true if provider is added, false otherwise
393      */
addOrUpdateProvider(PasspointConfiguration config, int uid, String packageName, boolean isFromSuggestion, boolean isTrusted)394     public boolean addOrUpdateProvider(PasspointConfiguration config, int uid,
395             String packageName, boolean isFromSuggestion, boolean isTrusted) {
396         mWifiMetrics.incrementNumPasspointProviderInstallation();
397         if (config == null) {
398             Log.e(TAG, "Configuration not provided");
399             return false;
400         }
401         if (!config.validate()) {
402             Log.e(TAG, "Invalid configuration");
403             return false;
404         }
405         if (!(isFromSuggestion || isTrusted)) {
406             Log.e(TAG, "Set isTrusted to false on a non suggestion passpoint is not allowed");
407             return false;
408         }
409 
410         mWifiCarrierInfoManager.tryUpdateCarrierIdForPasspoint(config);
411         // Create a provider and install the necessary certificates and keys.
412         PasspointProvider newProvider = mObjectFactory.makePasspointProvider(config, mKeyStore,
413                 mWifiCarrierInfoManager, mProviderIndex++, uid, packageName, isFromSuggestion);
414         newProvider.setTrusted(isTrusted);
415 
416         boolean metricsNoRootCa = false;
417         boolean metricsSelfSignedRootCa = false;
418         boolean metricsSubscriptionExpiration = false;
419 
420         if (config.getCredential().getUserCredential() != null
421                 || config.getCredential().getCertCredential() != null) {
422             X509Certificate[] x509Certificates = config.getCredential().getCaCertificates();
423             if (x509Certificates == null) {
424                 metricsNoRootCa = true;
425             } else {
426                 try {
427                     for (X509Certificate certificate : x509Certificates) {
428                         verifyCaCert(certificate);
429                     }
430                 } catch (CertPathValidatorException e) {
431                     // A self signed Root CA will fail path validation checks with NO_TRUST_ANCHOR
432                     if (e.getReason() == NO_TRUST_ANCHOR) {
433                         metricsSelfSignedRootCa = true;
434                     }
435                 } catch (Exception e) {
436                     // Other exceptions, fall through, will be handled below
437                 }
438             }
439         }
440         if (config.getSubscriptionExpirationTimeMillis() != Long.MIN_VALUE) {
441             metricsSubscriptionExpiration = true;
442         }
443 
444         if (!newProvider.installCertsAndKeys()) {
445             Log.e(TAG, "Failed to install certificates and keys to keystore");
446             return false;
447         }
448 
449         // Remove existing provider with the same unique ID.
450         if (mProviders.containsKey(config.getUniqueId())) {
451             PasspointProvider old = mProviders.get(config.getUniqueId());
452             // If new profile is from suggestion and from a different App, ignore new profile,
453             // return false.
454             // If from same app, update it.
455             if (isFromSuggestion && !old.getPackageName().equals(packageName)) {
456                 newProvider.uninstallCertsAndKeys();
457                 return false;
458             }
459             Log.d(TAG, "Replacing configuration for FQDN: " + config.getHomeSp().getFqdn()
460                     + " and unique ID: " + config.getUniqueId());
461             old.uninstallCertsAndKeys();
462             mProviders.remove(config.getUniqueId());
463             // New profile changes the credential, remove the related WifiConfig.
464             if (!old.equals(newProvider)) {
465                 mWifiConfigManager.removePasspointConfiguredNetwork(
466                         newProvider.getWifiConfig().getKey());
467             } else {
468                 // If there is a config cached in WifiConfigManager, update it with new info.
469                 updateWifiConfigInWcmIfPresent(
470                         newProvider.getWifiConfig(), uid, packageName, isFromSuggestion);
471             }
472         }
473         newProvider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
474         mProviders.put(config.getUniqueId(), newProvider);
475         mWifiConfigManager.saveToStore(true /* forceWrite */);
476         if (!isFromSuggestion && newProvider.getPackageName() != null) {
477             startTrackingAppOpsChange(newProvider.getPackageName(), uid);
478         }
479         Log.d(TAG, "Added/updated Passpoint configuration for FQDN: "
480                 + config.getHomeSp().getFqdn() + " with unique ID: " + config.getUniqueId()
481                 + " by UID: " + uid);
482         if (metricsNoRootCa) {
483             mWifiMetrics.incrementNumPasspointProviderWithNoRootCa();
484         }
485         if (metricsSelfSignedRootCa) {
486             mWifiMetrics.incrementNumPasspointProviderWithSelfSignedRootCa();
487         }
488         if (metricsSubscriptionExpiration) {
489             mWifiMetrics.incrementNumPasspointProviderWithSubscriptionExpiration();
490         }
491         mWifiMetrics.incrementNumPasspointProviderInstallSuccess();
492         return true;
493     }
494 
removeProviderInternal(PasspointProvider provider, int callingUid, boolean privileged)495     private boolean removeProviderInternal(PasspointProvider provider, int callingUid,
496             boolean privileged) {
497         if (!privileged && callingUid != provider.getCreatorUid()) {
498             Log.e(TAG, "UID " + callingUid + " cannot remove profile created by "
499                     + provider.getCreatorUid());
500             return false;
501         }
502         provider.uninstallCertsAndKeys();
503         String packageName = provider.getPackageName();
504         // Remove any configs corresponding to the profile in WifiConfigManager.
505         mWifiConfigManager.removePasspointConfiguredNetwork(
506                 provider.getWifiConfig().getKey());
507         String uniqueId = provider.getConfig().getUniqueId();
508         mProviders.remove(uniqueId);
509         mWifiConfigManager.saveToStore(true /* forceWrite */);
510 
511         // Stop monitoring the package if there is no Passpoint profile installed by the package
512         if (mAppOpsChangedListenerPerApp.containsKey(packageName)
513                 && getPasspointProviderWithPackage(packageName).size() == 0) {
514             stopTrackingAppOpsChange(packageName);
515         }
516         Log.d(TAG, "Removed Passpoint configuration: " + uniqueId);
517         mWifiMetrics.incrementNumPasspointProviderUninstallSuccess();
518         return true;
519     }
520 
521     /**
522      * Remove a Passpoint provider identified by the given its unique identifier.
523      *
524      * @param callingUid Calling UID.
525      * @param privileged Whether the caller is a privileged entity
526      * @param uniqueId The ID of the provider to remove. Not required if FQDN is specified.
527      * @param fqdn The FQDN of the provider to remove. Not required if unique ID is specified.
528      * @return true if a provider is removed, false otherwise
529      */
removeProvider(int callingUid, boolean privileged, String uniqueId, String fqdn)530     public boolean removeProvider(int callingUid, boolean privileged, String uniqueId,
531             String fqdn) {
532         if (uniqueId == null && fqdn == null) {
533             mWifiMetrics.incrementNumPasspointProviderUninstallation();
534             Log.e(TAG, "Cannot remove provider, both FQDN and unique ID are null");
535             return false;
536         }
537 
538         if (uniqueId != null) {
539             // Unique identifier provided
540             mWifiMetrics.incrementNumPasspointProviderUninstallation();
541             PasspointProvider provider = mProviders.get(uniqueId);
542             if (provider == null) {
543                 Log.e(TAG, "Config doesn't exist");
544                 return false;
545             }
546             return removeProviderInternal(provider, callingUid, privileged);
547         }
548 
549         // FQDN provided, loop through all profiles with matching FQDN
550         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
551         int removedProviders = 0;
552         int numOfUninstallations = 0;
553         for (PasspointProvider provider : passpointProviders) {
554             if (!TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
555                 continue;
556             }
557             mWifiMetrics.incrementNumPasspointProviderUninstallation();
558             numOfUninstallations++;
559             if (removeProviderInternal(provider, callingUid, privileged)) {
560                 removedProviders++;
561             }
562         }
563 
564         if (numOfUninstallations == 0) {
565             // Update uninstallation requests metrics here to cover the corner case of trying to
566             // uninstall a non-existent provider.
567             mWifiMetrics.incrementNumPasspointProviderUninstallation();
568         }
569 
570         return removedProviders > 0;
571     }
572 
573     /**
574      * Enable or disable the auto-join configuration. Auto-join controls whether or not the
575      * passpoint configuration is used for auto connection (network selection). Note that even
576      * when auto-join is disabled the configuration can still be used for manual connection.
577      *
578      * @param uniqueId The unique identifier of the configuration. Not required if FQDN is specified
579      * @param fqdn The FQDN of the configuration. Not required if uniqueId is specified.
580      * @param enableAutojoin true to enable auto-join, false to disable.
581      * @return true on success, false otherwise (e.g. if no such provider exists).
582      */
enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin)583     public boolean enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin) {
584         if (uniqueId == null && fqdn == null) {
585             return false;
586         }
587         if (uniqueId != null) {
588             // Unique identifier provided
589             PasspointProvider provider = mProviders.get(uniqueId);
590             if (provider == null) {
591                 Log.e(TAG, "Config doesn't exist");
592                 return false;
593             }
594             if (provider.setAutojoinEnabled(enableAutojoin)) {
595                 mWifiMetrics.logUserActionEvent(enableAutojoin
596                                 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
597                                 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
598                         provider.isFromSuggestion(), true);
599             }
600             mWifiConfigManager.saveToStore(true);
601             return true;
602         }
603 
604         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
605         boolean found = false;
606 
607         // FQDN provided, loop through all profiles with matching FQDN
608         for (PasspointProvider provider : passpointProviders) {
609             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
610                 if (provider.setAutojoinEnabled(enableAutojoin)) {
611                     mWifiMetrics.logUserActionEvent(enableAutojoin
612                                     ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
613                                     : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
614                             provider.isFromSuggestion(), true);
615                 }
616                 found = true;
617             }
618         }
619         if (found) {
620             mWifiConfigManager.saveToStore(true);
621         }
622         return found;
623     }
624 
625     /**
626      * Enable or disable MAC randomization for this passpoint profile.
627      * @param fqdn The FQDN of the configuration
628      * @param enable true to enable MAC randomization, false to disable
629      * @return true on success, false otherwise (e.g. if no such provider exists).
630      */
enableMacRandomization(@onNull String fqdn, boolean enable)631     public boolean enableMacRandomization(@NonNull String fqdn, boolean enable) {
632         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
633         boolean found = false;
634 
635         // Loop through all profiles with matching FQDN
636         for (PasspointProvider provider : passpointProviders) {
637             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
638                 boolean settingChanged = provider.setMacRandomizationEnabled(enable);
639                 if (settingChanged) {
640                     mWifiMetrics.logUserActionEvent(enable
641                                     ? UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON
642                                     : UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF,
643                             provider.isFromSuggestion(), true);
644                     mWifiConfigManager.removePasspointConfiguredNetwork(
645                             provider.getWifiConfig().getKey());
646                 }
647                 found = true;
648             }
649         }
650         if (found) {
651             mWifiConfigManager.saveToStore(true);
652         }
653         return found;
654     }
655 
656     /**
657      * Set the metered override value for this passpoint profile
658      * @param fqdn The FQDN of the configuration
659      * @param meteredOverride One of the values in {@link MeteredOverride}
660      * @return true on success, false otherwise (e.g. if no such provider exists).
661      */
setMeteredOverride(@onNull String fqdn, @MeteredOverride int meteredOverride)662     public boolean setMeteredOverride(@NonNull String fqdn, @MeteredOverride int meteredOverride) {
663         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
664         boolean found = false;
665 
666         // Loop through all profiles with matching FQDN
667         for (PasspointProvider provider : passpointProviders) {
668             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
669                 if (provider.setMeteredOverride(meteredOverride)) {
670                     mWifiMetrics.logUserActionEvent(
671                             WifiMetrics.convertMeteredOverrideEnumToUserActionEventType(
672                                     meteredOverride),
673                             provider.isFromSuggestion(), true);
674                 }
675                 found = true;
676             }
677         }
678         if (found) {
679             mWifiConfigManager.saveToStore(true);
680         }
681         return found;
682     }
683 
684     /**
685      * Return the installed Passpoint provider configurations.
686      * An empty list will be returned when no provider is installed.
687      *
688      * @param callingUid Calling UID.
689      * @param privileged Whether the caller is a privileged entity
690      * @return A list of {@link PasspointConfiguration}
691      */
getProviderConfigs(int callingUid, boolean privileged)692     public List<PasspointConfiguration> getProviderConfigs(int callingUid,
693             boolean privileged) {
694         List<PasspointConfiguration> configs = new ArrayList<>();
695         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
696             PasspointProvider provider = entry.getValue();
697             if (privileged || callingUid == provider.getCreatorUid()) {
698                 if (provider.isFromSuggestion()) {
699                     continue;
700                 }
701                 configs.add(provider.getConfig());
702             }
703         }
704         return configs;
705     }
706 
707     /**
708      * Find all providers that can provide service through the given AP, which means the
709      * providers contained credential to authenticate with the given AP.
710      *
711      * If there is any home provider available, will return a list of matched home providers.
712      * Otherwise will return a list of matched roaming providers.
713      *
714      * A empty list will be returned if no matching is found.
715      *
716      * @param scanResult The scan result associated with the AP
717      * @return a list of pairs of {@link PasspointProvider} and match status.
718      */
matchProvider( ScanResult scanResult)719     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider(
720             ScanResult scanResult) {
721         return matchProvider(scanResult, true);
722     }
723 
724     /**
725      * Find all providers that can provide service through the given AP, which means the
726      * providers contained credential to authenticate with the given AP.
727      *
728      * If there is any home provider available, will return a list of matched home providers.
729      * Otherwise will return a list of matched roaming providers.
730      *
731      * A empty list will be returned if no matching is found.
732      *
733      * @param scanResult The scan result associated with the AP
734      * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty
735      * @return a list of pairs of {@link PasspointProvider} and match status.
736      */
matchProvider( ScanResult scanResult, boolean anqpRequestAllowed)737     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider(
738             ScanResult scanResult, boolean anqpRequestAllowed) {
739         List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders(
740                 scanResult, anqpRequestAllowed);
741         if (allMatches.isEmpty()) {
742             return allMatches;
743         }
744         List<Pair<PasspointProvider, PasspointMatch>> homeProviders = new ArrayList<>();
745         List<Pair<PasspointProvider, PasspointMatch>> roamingProviders = new ArrayList<>();
746         for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
747             if (isExpired(match.first.getConfig())) {
748                 continue;
749             }
750             if (match.second == PasspointMatch.HomeProvider) {
751                 homeProviders.add(match);
752             } else {
753                 roamingProviders.add(match);
754             }
755         }
756 
757         if (!homeProviders.isEmpty()) {
758             Log.d(TAG, String.format("Matched %s to %s providers as %s", scanResult.SSID,
759                     homeProviders.size(), "Home Provider"));
760             return homeProviders;
761         }
762 
763         if (!roamingProviders.isEmpty()) {
764             Log.d(TAG, String.format("Matched %s to %s providers as %s", scanResult.SSID,
765                     roamingProviders.size(), "Roaming Provider"));
766             return roamingProviders;
767         }
768 
769         if (mVerboseLoggingEnabled) {
770             Log.d(TAG, "No service provider found for " + scanResult.SSID);
771         }
772         return new ArrayList<>();
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 @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
782             ScanResult scanResult) {
783         return getAllMatchedProviders(scanResult, true);
784     }
785 
786     /**
787      * Return a list of all providers that can provide service through the given AP.
788      *
789      * @param scanResult The scan result associated with the AP
790      * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty
791      * @return a list of pairs of {@link PasspointProvider} and match status.
792      */
getAllMatchedProviders( ScanResult scanResult, boolean anqpRequestAllowed)793     private @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
794             ScanResult scanResult, boolean anqpRequestAllowed) {
795         List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>();
796 
797         // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
798         // Vendor Specific IE.
799         InformationElementUtil.RoamingConsortium roamingConsortium =
800                 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
801         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
802                 scanResult.informationElements);
803 
804         // Lookup ANQP data in the cache.
805         long bssid;
806         try {
807             bssid = Utils.parseMac(scanResult.BSSID);
808         } catch (IllegalArgumentException e) {
809             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
810             return allMatches;
811         }
812         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
813                 vsa.anqpDomainID);
814         ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
815         if (anqpEntry == null) {
816             if (anqpRequestAllowed) {
817                 mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
818                         roamingConsortium.anqpOICount > 0, vsa.hsRelease);
819             }
820             Log.d(TAG, "ANQP entry not found for: " + anqpKey);
821             return allMatches;
822         }
823         boolean anyProviderUpdated = false;
824         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
825             PasspointProvider provider = entry.getValue();
826             if (provider.tryUpdateCarrierId()) {
827                 anyProviderUpdated = true;
828             }
829             if (mVerboseLoggingEnabled) {
830                 Log.d(TAG, "Matching provider " + provider.getConfig().getHomeSp().getFqdn()
831                         + " with "
832                         + anqpEntry.getElements().get(Constants.ANQPElementType.ANQPDomName));
833             }
834             PasspointMatch matchStatus = provider.match(anqpEntry.getElements(),
835                     roamingConsortium);
836             if (matchStatus == PasspointMatch.HomeProvider
837                     || matchStatus == PasspointMatch.RoamingProvider) {
838                 allMatches.add(Pair.create(provider, matchStatus));
839             }
840         }
841         if (anyProviderUpdated) {
842             mWifiConfigManager.saveToStore(true);
843         }
844         if (allMatches.size() != 0) {
845             for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
846                 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
847                         match.first.getConfig().getHomeSp().getFqdn(),
848                         match.second == PasspointMatch.HomeProvider ? "Home Provider"
849                                 : "Roaming Provider"));
850             }
851         } else {
852             if (mVerboseLoggingEnabled) {
853                 Log.d(TAG, "No service providers found for " + scanResult.SSID);
854             }
855         }
856         return allMatches;
857     }
858 
859     /**
860      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the
861      * current {@link PasspointManager}.
862      *
863      * This will not trigger a config store write, since this will be invoked as part of the
864      * configuration migration, the caller will be responsible for triggering store write
865      * after the migration is completed.
866      *
867      * @param config {@link WifiConfiguration} representation of the Passpoint configuration
868      * @return true on success
869      */
addLegacyPasspointConfig(WifiConfiguration config)870     public static boolean addLegacyPasspointConfig(WifiConfiguration config) {
871         if (sPasspointManager == null) {
872             Log.e(TAG, "PasspointManager have not been initialized yet");
873             return false;
874         }
875         Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN);
876         return sPasspointManager.addWifiConfig(config);
877     }
878 
879     /**
880      * Sweep the ANQP cache to remove expired entries.
881      */
sweepCache()882     public void sweepCache() {
883         mAnqpCache.sweep();
884     }
885 
886     /**
887      * Notify the completion of an ANQP request.
888      * TODO(zqiu): currently the notification is done through WifiMonitor,
889      * will no longer be the case once we switch over to use wificond.
890      */
notifyANQPDone(AnqpEvent anqpEvent)891     public void notifyANQPDone(AnqpEvent anqpEvent) {
892         mPasspointEventHandler.notifyANQPDone(anqpEvent);
893     }
894 
895     /**
896      * Notify the completion of an icon request.
897      * TODO(zqiu): currently the notification is done through WifiMonitor,
898      * will no longer be the case once we switch over to use wificond.
899      */
notifyIconDone(IconEvent iconEvent)900     public void notifyIconDone(IconEvent iconEvent) {
901         mPasspointEventHandler.notifyIconDone(iconEvent);
902     }
903 
904     /**
905      * Notify the reception of a Wireless Network Management (WNM) frame.
906      * TODO(zqiu): currently the notification is done through WifiMonitor,
907      * will no longer be the case once we switch over to use wificond.
908      */
receivedWnmFrame(WnmData data)909     public void receivedWnmFrame(WnmData data) {
910         mPasspointEventHandler.notifyWnmFrameReceived(data);
911     }
912 
913     /**
914      * Request the specified icon file |fileName| from the specified AP |bssid|.
915      * @return true if the request is sent successfully, false otherwise
916      */
queryPasspointIcon(long bssid, String fileName)917     public boolean queryPasspointIcon(long bssid, String fileName) {
918         return mPasspointEventHandler.requestIcon(bssid, fileName);
919     }
920 
921     /**
922      * Lookup the ANQP elements associated with the given AP from the cache. An empty map
923      * will be returned if no match found in the cache.
924      *
925      * @param scanResult The scan result associated with the AP
926      * @return Map of ANQP elements
927      */
getANQPElements(ScanResult scanResult)928     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) {
929         // Retrieve the Hotspot 2.0 Vendor Specific IE.
930         InformationElementUtil.Vsa vsa =
931                 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements);
932 
933         // Lookup ANQP data in the cache.
934         long bssid;
935         try {
936             bssid = Utils.parseMac(scanResult.BSSID);
937         } catch (IllegalArgumentException e) {
938             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
939             return new HashMap<>();
940         }
941         ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey(
942                 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID));
943         if (anqpEntry != null) {
944             return anqpEntry.getElements();
945         }
946         return new HashMap<>();
947     }
948 
949     /**
950      * Return a map of all matching configurations keys with corresponding scanResults (or an empty
951      * map if none).
952      *
953      * @param scanResults The list of scan results
954      * @return Map that consists of identifies and corresponding scanResults per network type
955      * ({@link WifiManager#PASSPOINT_HOME_NETWORK}, {@link WifiManager#PASSPOINT_ROAMING_NETWORK}).
956      */
957     public Map<String, Map<Integer, List<ScanResult>>>
getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults)958             getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults) {
959         if (scanResults == null) {
960             Log.e(TAG, "Attempt to get matching config for a null ScanResults");
961             return new HashMap<>();
962         }
963         Map<String, Map<Integer, List<ScanResult>>> configs = new HashMap<>();
964 
965         for (ScanResult scanResult : scanResults) {
966             if (!scanResult.isPasspointNetwork()) continue;
967             List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders(
968                     scanResult);
969             for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) {
970                 WifiConfiguration config = matchedProvider.first.getWifiConfig();
971                 int type = WifiManager.PASSPOINT_HOME_NETWORK;
972                 if (!config.isHomeProviderNetwork) {
973                     type = WifiManager.PASSPOINT_ROAMING_NETWORK;
974                 }
975                 Map<Integer, List<ScanResult>> scanResultsPerNetworkType =
976                         configs.get(config.getKey());
977                 if (scanResultsPerNetworkType == null) {
978                     scanResultsPerNetworkType = new HashMap<>();
979                     configs.put(config.getKey(), scanResultsPerNetworkType);
980                 }
981                 List<ScanResult> matchingScanResults = scanResultsPerNetworkType.get(type);
982                 if (matchingScanResults == null) {
983                     matchingScanResults = new ArrayList<>();
984                     scanResultsPerNetworkType.put(type, matchingScanResults);
985                 }
986                 matchingScanResults.add(scanResult);
987             }
988         }
989 
990         return configs;
991     }
992 
993     /**
994      * Returns the list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given list
995      * of ScanResult.
996      *
997      * An empty map will be returned when an invalid scanResults are provided or no match is found.
998      *
999      * @param scanResults a list of ScanResult that has Passpoint APs.
1000      * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult}
1001      */
getMatchingOsuProviders( List<ScanResult> scanResults)1002     public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders(
1003             List<ScanResult> scanResults) {
1004         if (scanResults == null) {
1005             Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult");
1006             return new HashMap();
1007         }
1008 
1009         Map<OsuProvider, List<ScanResult>> osuProviders = new HashMap<>();
1010         for (ScanResult scanResult : scanResults) {
1011             if (!scanResult.isPasspointNetwork()) continue;
1012 
1013             // Lookup OSU Providers ANQP element.
1014             Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult);
1015             if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
1016                 continue;
1017             }
1018             HSOsuProvidersElement element =
1019                     (HSOsuProvidersElement) anqpElements.get(
1020                             Constants.ANQPElementType.HSOSUProviders);
1021             for (OsuProviderInfo info : element.getProviders()) {
1022                 // Set null for OSU-SSID in the class because OSU-SSID is a factor for hotspot
1023                 // operator rather than service provider, which means it can be different for
1024                 // each hotspot operators.
1025                 OsuProvider provider = new OsuProvider((WifiSsid) null, info.getFriendlyNames(),
1026                         info.getServiceDescription(), info.getServerUri(),
1027                         info.getNetworkAccessIdentifier(), info.getMethodList());
1028                 List<ScanResult> matchingScanResults = osuProviders.get(provider);
1029                 if (matchingScanResults == null) {
1030                     matchingScanResults = new ArrayList<>();
1031                     osuProviders.put(provider, matchingScanResults);
1032                 }
1033                 matchingScanResults.add(scanResult);
1034             }
1035         }
1036         return osuProviders;
1037     }
1038 
1039     /**
1040      * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) providers
1041      *
1042      * An empty map will be returned when an invalid {@code osuProviders} are provided or no match
1043      * is found.
1044      *
1045      * @param osuProviders a list of {@link OsuProvider}
1046      * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
1047      */
getMatchingPasspointConfigsForOsuProviders( List<OsuProvider> osuProviders)1048     public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
1049             List<OsuProvider> osuProviders) {
1050         Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>();
1051 
1052         for (OsuProvider osuProvider : osuProviders) {
1053             Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList();
1054             if (friendlyNamesForOsuProvider == null) continue;
1055             for (PasspointProvider provider : mProviders.values()) {
1056                 PasspointConfiguration passpointConfiguration = provider.getConfig();
1057                 Map<String, String> serviceFriendlyNamesForPpsMo =
1058                         passpointConfiguration.getServiceFriendlyNames();
1059                 if (serviceFriendlyNamesForPpsMo == null) continue;
1060 
1061                 for (Map.Entry<String, String> entry : serviceFriendlyNamesForPpsMo.entrySet()) {
1062                     String lang = entry.getKey();
1063                     String friendlyName = entry.getValue();
1064                     if (friendlyName == null) continue;
1065                     String osuFriendlyName = friendlyNamesForOsuProvider.get(lang);
1066                     if (osuFriendlyName == null) continue;
1067                     if (friendlyName.equals(osuFriendlyName)) {
1068                         matchingPasspointConfigs.put(osuProvider, passpointConfiguration);
1069                         break;
1070                     }
1071                 }
1072             }
1073         }
1074         return matchingPasspointConfigs;
1075     }
1076 
1077     /**
1078      * Returns the corresponding wifi configurations for given a list Passpoint profile unique
1079      * identifiers.
1080      *
1081      * An empty list will be returned when no match is found.
1082      *
1083      * @param idList a list of unique identifiers
1084      * @return List of {@link WifiConfiguration} converted from {@link PasspointProvider}
1085      */
getWifiConfigsForPasspointProfiles(List<String> idList)1086     public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> idList) {
1087         if (mProviders.isEmpty()) {
1088             return Collections.emptyList();
1089         }
1090         List<WifiConfiguration> configs = new ArrayList<>();
1091         Set<String> uniqueIdSet = new HashSet<>();
1092         uniqueIdSet.addAll(idList);
1093         for (String uniqueId : uniqueIdSet) {
1094             PasspointProvider provider = mProviders.get(uniqueId);
1095             if (provider == null) {
1096                 continue;
1097             }
1098             WifiConfiguration config = provider.getWifiConfig();
1099             // If the Passpoint configuration is from a suggestion, check if the app shares this
1100             // suggestion with the user.
1101             if (provider.isFromSuggestion()
1102                     && !mWifiInjector.getWifiNetworkSuggestionsManager()
1103                     .isPasspointSuggestionSharedWithUser(config)) {
1104                 continue;
1105             }
1106             configs.add(config);
1107         }
1108         return configs;
1109     }
1110 
1111     /**
1112      * Invoked when a Passpoint network was successfully connected based on the credentials
1113      * provided by the given Passpoint provider
1114      *
1115      * @param uniqueId The unique identifier of the Passpointprofile
1116      */
onPasspointNetworkConnected(String uniqueId)1117     public void onPasspointNetworkConnected(String uniqueId) {
1118         PasspointProvider provider = mProviders.get(uniqueId);
1119         if (provider == null) {
1120             Log.e(TAG, "Passpoint network connected without provider: " + uniqueId);
1121             return;
1122         }
1123         if (!provider.getHasEverConnected()) {
1124             // First successful connection using this provider.
1125             provider.setHasEverConnected(true);
1126         }
1127     }
1128 
1129     /**
1130      * Update metrics related to installed Passpoint providers, this includes the number of
1131      * installed providers and the number of those providers that results in a successful network
1132      * connection.
1133      */
updateMetrics()1134     public void updateMetrics() {
1135         int numProviders = mProviders.size();
1136         int numConnectedProviders = 0;
1137         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
1138             if (entry.getValue().getHasEverConnected()) {
1139                 numConnectedProviders++;
1140             }
1141         }
1142         mWifiMetrics.updateSavedPasspointProfilesInfo(mProviders);
1143         mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders);
1144     }
1145 
1146     /**
1147      * Dump the current state of PasspointManager to the provided output stream.
1148      *
1149      * @param pw The output stream to write to
1150      */
dump(PrintWriter pw)1151     public void dump(PrintWriter pw) {
1152         pw.println("Dump of PasspointManager");
1153         pw.println("PasspointManager - Providers Begin ---");
1154         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
1155             pw.println(entry.getValue());
1156         }
1157         pw.println("PasspointManager - Providers End ---");
1158         pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex);
1159         mAnqpCache.dump(pw);
1160         mAnqpRequestManager.dump(pw);
1161     }
1162 
1163     /**
1164      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}.
1165      *
1166      * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration
1167      * @return true on success
1168      */
addWifiConfig(WifiConfiguration wifiConfig)1169     private boolean addWifiConfig(WifiConfiguration wifiConfig) {
1170         if (wifiConfig == null) {
1171             return false;
1172         }
1173 
1174         // Convert to PasspointConfiguration
1175         PasspointConfiguration passpointConfig =
1176                 PasspointProvider.convertFromWifiConfig(wifiConfig);
1177         if (passpointConfig == null) {
1178             return false;
1179         }
1180 
1181         // Setup aliases for enterprise certificates and key.
1182         WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
1183         String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias();
1184         String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias();
1185         if (passpointConfig.getCredential().getUserCredential() != null
1186                 && TextUtils.isEmpty(caCertificateAliasSuffix)) {
1187             Log.e(TAG, "Missing CA Certificate for user credential");
1188             return false;
1189         }
1190         if (passpointConfig.getCredential().getCertCredential() != null) {
1191             if (TextUtils.isEmpty(caCertificateAliasSuffix)) {
1192                 Log.e(TAG, "Missing CA certificate for Certificate credential");
1193                 return false;
1194             }
1195             if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) {
1196                 Log.e(TAG, "Missing client certificate and key for certificate credential");
1197                 return false;
1198             }
1199         }
1200 
1201         // Note that for legacy configuration, the alias for client private key is the same as the
1202         // alias for the client certificate.
1203         PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore,
1204                 mWifiCarrierInfoManager,
1205                 mProviderIndex++, wifiConfig.creatorUid, null, false,
1206                 Arrays.asList(enterpriseConfig.getCaCertificateAlias()),
1207                 enterpriseConfig.getClientCertificateAlias(), null, false, false);
1208         provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
1209         mProviders.put(passpointConfig.getUniqueId(), provider);
1210         return true;
1211     }
1212 
1213     /**
1214      * Start the subscription provisioning flow with a provider.
1215      * @param callingUid integer indicating the uid of the caller
1216      * @param provider {@link OsuProvider} the provider to subscribe to
1217      * @param callback {@link IProvisioningCallback} callback to update status to the caller
1218      * @return boolean return value from the provisioning method
1219      */
startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)1220     public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
1221             IProvisioningCallback callback) {
1222         return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback);
1223     }
1224 
1225     /**
1226      * Check if a Passpoint configuration is expired
1227      *
1228      * @param config {@link PasspointConfiguration} Passpoint configuration
1229      * @return True if the configuration is expired, false if not or expiration is unset
1230      */
isExpired(@onNull PasspointConfiguration config)1231     private boolean isExpired(@NonNull PasspointConfiguration config) {
1232         long expirationTime = config.getSubscriptionExpirationTimeMillis();
1233 
1234         if (expirationTime != Long.MIN_VALUE) {
1235             long curTime = System.currentTimeMillis();
1236 
1237             // Check expiration and return true for expired profiles
1238             if (curTime >= expirationTime) {
1239                 Log.d(TAG, "Profile for " + config.getServiceFriendlyName() + " has expired, "
1240                         + "expiration time: " + expirationTime + ", current time: "
1241                         + curTime);
1242                 return true;
1243             }
1244         }
1245         return false;
1246     }
1247 
1248     /**
1249      * Get the filtered ScanResults which could be served by the {@link PasspointConfiguration}.
1250      * @param passpointConfiguration The instance of {@link PasspointConfiguration}
1251      * @param scanResults The list of {@link ScanResult}
1252      * @return The filtered ScanResults
1253      */
1254     @NonNull
getMatchingScanResults( @onNull PasspointConfiguration passpointConfiguration, @NonNull List<ScanResult> scanResults)1255     public List<ScanResult> getMatchingScanResults(
1256             @NonNull PasspointConfiguration passpointConfiguration,
1257             @NonNull List<ScanResult> scanResults) {
1258         PasspointProvider provider = mObjectFactory.makePasspointProvider(passpointConfiguration,
1259                 null, mWifiCarrierInfoManager, 0, 0, null, false);
1260         List<ScanResult> filteredScanResults = new ArrayList<>();
1261         for (ScanResult scanResult : scanResults) {
1262             PasspointMatch matchInfo = provider.match(getANQPElements(scanResult),
1263                     InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements));
1264             if (matchInfo == PasspointMatch.HomeProvider
1265                     || matchInfo == PasspointMatch.RoamingProvider) {
1266                 filteredScanResults.add(scanResult);
1267             }
1268         }
1269 
1270         return filteredScanResults;
1271     }
1272 
1273     /**
1274      * Check if the providers list is empty
1275      *
1276      * @return true if the providers list is empty, false otherwise
1277      */
isProvidersListEmpty()1278     public boolean isProvidersListEmpty() {
1279         return mProviders.isEmpty();
1280     }
1281 
1282     /**
1283      * Clear ANQP requests and flush ANQP Cache (for factory reset)
1284      */
clearAnqpRequestsAndFlushCache()1285     public void clearAnqpRequestsAndFlushCache() {
1286         mAnqpRequestManager.clear();
1287         mAnqpCache.flush();
1288     }
1289 
1290     /**
1291      * Verify that the given certificate is trusted by one of the pre-loaded public CAs in the
1292      * system key store.
1293      *
1294      * @param caCert The CA Certificate to verify
1295      * @throws CertPathValidatorException
1296      * @throws Exception
1297      */
verifyCaCert(X509Certificate caCert)1298     private void verifyCaCert(X509Certificate caCert)
1299             throws GeneralSecurityException, IOException {
1300         CertificateFactory factory = CertificateFactory.getInstance("X.509");
1301         CertPathValidator validator =
1302                 CertPathValidator.getInstance(CertPathValidator.getDefaultType());
1303         CertPath path = factory.generateCertPath(Arrays.asList(caCert));
1304         KeyStore ks = KeyStore.getInstance("AndroidCAStore");
1305         ks.load(null, null);
1306         PKIXParameters params = new PKIXParameters(ks);
1307         params.setRevocationEnabled(false);
1308         validator.validate(path, params);
1309     }
1310 }
1311