• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.hotspot2;
18 
19 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE;
20 import static android.net.wifi.WifiConfiguration.MeteredOverride;
21 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS;
22 
23 import static java.security.cert.PKIXReason.NO_TRUST_ANCHOR;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.ActivityManager;
28 import android.app.AppOpsManager;
29 import android.content.Context;
30 import android.net.MacAddress;
31 import android.net.wifi.ScanResult;
32 import android.net.wifi.WifiConfiguration;
33 import android.net.wifi.WifiEnterpriseConfig;
34 import android.net.wifi.WifiManager;
35 import android.net.wifi.WifiSsid;
36 import android.net.wifi.hotspot2.IProvisioningCallback;
37 import android.net.wifi.hotspot2.OsuProvider;
38 import android.net.wifi.hotspot2.PasspointConfiguration;
39 import android.os.Handler;
40 import android.os.Looper;
41 import android.os.Process;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.Pair;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.modules.utils.build.SdkLevel;
48 import com.android.server.wifi.Clock;
49 import com.android.server.wifi.MacAddressUtil;
50 import com.android.server.wifi.NetworkUpdateResult;
51 import com.android.server.wifi.WifiCarrierInfoManager;
52 import com.android.server.wifi.WifiConfigManager;
53 import com.android.server.wifi.WifiConfigStore;
54 import com.android.server.wifi.WifiInjector;
55 import com.android.server.wifi.WifiKeyStore;
56 import com.android.server.wifi.WifiMetrics;
57 import com.android.server.wifi.WifiNative;
58 import com.android.server.wifi.WifiSettingsStore;
59 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
60 import com.android.server.wifi.hotspot2.anqp.Constants;
61 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
62 import com.android.server.wifi.hotspot2.anqp.I18Name;
63 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
64 import com.android.server.wifi.hotspot2.anqp.VenueNameElement;
65 import com.android.server.wifi.hotspot2.anqp.VenueUrlElement;
66 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
67 import com.android.server.wifi.util.InformationElementUtil;
68 import com.android.server.wifi.util.WifiPermissionsUtil;
69 
70 import java.io.IOException;
71 import java.io.PrintWriter;
72 import java.net.URL;
73 import java.security.GeneralSecurityException;
74 import java.security.KeyStore;
75 import java.security.cert.CertPath;
76 import java.security.cert.CertPathValidator;
77 import java.security.cert.CertPathValidatorException;
78 import java.security.cert.CertificateFactory;
79 import java.security.cert.PKIXParameters;
80 import java.security.cert.X509Certificate;
81 import java.util.ArrayList;
82 import java.util.Arrays;
83 import java.util.Collections;
84 import java.util.HashMap;
85 import java.util.HashSet;
86 import java.util.List;
87 import java.util.Locale;
88 import java.util.Map;
89 import java.util.Set;
90 import java.util.stream.Collectors;
91 
92 /**
93  * This class provides the APIs to manage Passpoint provider configurations.
94  * It deals with the following:
95  * - Maintaining a list of configured Passpoint providers for provider matching.
96  * - Persisting the providers configurations to store when required.
97  * - matching Passpoint providers based on the scan results
98  * - Supporting WifiManager Public API calls:
99  *   > addOrUpdatePasspointConfiguration()
100  *   > removePasspointConfiguration()
101  *   > getPasspointConfigurations()
102  *
103  * The provider matching requires obtaining additional information from the AP (ANQP elements).
104  * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests.
105  *
106  * NOTE: These API's are not thread safe and should only be used from the main Wifi thread.
107  */
108 public class PasspointManager {
109     private static final String TAG = "PasspointManager";
110 
111     /**
112      * Handle for the current {@link PasspointManager} instance.  This is needed to avoid
113      * circular dependency with the WifiConfigManger, it will be used for adding the
114      * legacy Passpoint configurations.
115      *
116      * This can be eliminated once we can remove the dependency for WifiConfigManager (for
117      * triggering config store write) from this class.
118      */
119     private static PasspointManager sPasspointManager;
120 
121     private final PasspointEventHandler mPasspointEventHandler;
122     private final WifiInjector mWifiInjector;
123     private final Handler mHandler;
124     private final WifiKeyStore mKeyStore;
125     private final PasspointObjectFactory mObjectFactory;
126 
127     private final Map<String, PasspointProvider> mProviders;
128     private final AnqpCache mAnqpCache;
129     private final ANQPRequestManager mAnqpRequestManager;
130     private final WifiConfigManager mWifiConfigManager;
131     private final WifiMetrics mWifiMetrics;
132     private final PasspointProvisioner mPasspointProvisioner;
133     private PasspointNetworkNominateHelper mPasspointNetworkNominateHelper;
134     private final AppOpsManager mAppOps;
135     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
136     private final MacAddressUtil mMacAddressUtil;
137     private final Clock mClock;
138     private final WifiPermissionsUtil mWifiPermissionsUtil;
139     private final WifiSettingsStore mSettingsStore;
140     private final boolean mIsLowMemory;
141 
142     /**
143      * Map of package name of an app to the app ops changed listener for the app.
144      */
145     private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>();
146 
147     // Counter used for assigning unique identifier to each provider.
148     private long mProviderIndex;
149     private boolean mVerboseLoggingEnabled = false;
150     // Set default value to false before receiving boot completed event.
151     private boolean mEnabled = false;
152 
153     private class CallbackHandler implements PasspointEventHandler.Callbacks {
154         private final Context mContext;
CallbackHandler(Context context)155         CallbackHandler(Context context) {
156             mContext = context;
157         }
158 
159         @Override
onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements)160         public void onANQPResponse(long bssid,
161                 Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
162             if (mVerboseLoggingEnabled) {
163                 Log.d(TAG, "ANQP response received from BSSID "
164                         + Utils.macToString(bssid) + " - List of ANQP elements:");
165                 int i = 0;
166                 for (Constants.ANQPElementType type : anqpElements.keySet()) {
167                     Log.d(TAG, "#" + i++ + ": " + type);
168                 }
169             }
170             // Notify request manager for the completion of a request.
171             ANQPNetworkKey anqpKey =
172                     mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null);
173             if (anqpElements == null || anqpKey == null) {
174                 // Query failed or the request wasn't originated from us (not tracked by the
175                 // request manager). Nothing to be done.
176                 return;
177             }
178 
179             if (anqpElements.containsKey(Constants.ANQPElementType.ANQPVenueUrl)) {
180                 // Venue URL ANQP is requested and received only after the network is connected
181                 mWifiMetrics.incrementTotalNumberOfPasspointConnectionsWithVenueUrl();
182             }
183 
184             // Add new entry to the cache.
185             mAnqpCache.addOrUpdateEntry(anqpKey, anqpElements);
186         }
187 
188         @Override
onIconResponse(long bssid, String fileName, byte[] data)189         public void onIconResponse(long bssid, String fileName, byte[] data) {
190             // Empty
191         }
192 
193         @Override
onWnmFrameReceived(WnmData event)194         public void onWnmFrameReceived(WnmData event) {
195             // Empty
196         }
197     }
198 
199     /**
200      * Data provider for the Passpoint configuration store data
201      * {@link PasspointConfigUserStoreData}.
202      */
203     private class UserDataSourceHandler implements PasspointConfigUserStoreData.DataSource {
204         @Override
getProviders()205         public List<PasspointProvider> getProviders() {
206             List<PasspointProvider> providers = new ArrayList<>();
207             for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
208                 providers.add(entry.getValue());
209             }
210             return providers;
211         }
212 
213         @Override
setProviders(List<PasspointProvider> providers)214         public void setProviders(List<PasspointProvider> providers) {
215             mProviders.clear();
216             for (PasspointProvider provider : providers) {
217                 provider.enableVerboseLogging(mVerboseLoggingEnabled);
218                 mProviders.put(provider.getConfig().getUniqueId(), provider);
219                 if (provider.getPackageName() != null) {
220                     startTrackingAppOpsChange(provider.getPackageName(),
221                             provider.getCreatorUid());
222                 }
223             }
224         }
225     }
226 
227     /**
228      * Data provider for the Passpoint configuration store data
229      * {@link PasspointConfigSharedStoreData}.
230      */
231     private class SharedDataSourceHandler implements PasspointConfigSharedStoreData.DataSource {
232         @Override
getProviderIndex()233         public long getProviderIndex() {
234             return mProviderIndex;
235         }
236 
237         @Override
setProviderIndex(long providerIndex)238         public void setProviderIndex(long providerIndex) {
239             mProviderIndex = providerIndex;
240         }
241     }
242 
243     /**
244      * Listener for app-ops changes for apps to remove the corresponding Passpoint profiles.
245      */
246     private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener {
247         private final String mPackageName;
248         private final int mUid;
249 
AppOpsChangedListener(@onNull String packageName, int uid)250         AppOpsChangedListener(@NonNull String packageName, int uid) {
251             mPackageName = packageName;
252             mUid = uid;
253         }
254 
255         @Override
onOpChanged(String op, String packageName)256         public void onOpChanged(String op, String packageName) {
257             mHandler.post(() -> {
258                 if (!mPackageName.equals(packageName)) return;
259                 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return;
260 
261                 // Ensures the uid to package mapping is still correct.
262                 try {
263                     mAppOps.checkPackage(mUid, mPackageName);
264                 } catch (SecurityException e) {
265                     Log.wtf(TAG, "Invalid uid/package" + packageName);
266                     return;
267                 }
268                 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName)
269                         == AppOpsManager.MODE_IGNORED) {
270                     Log.i(TAG, "User disallowed change wifi state for " + packageName);
271 
272                     // Removes the profiles installed by the app from database.
273                     removePasspointProviderWithPackage(mPackageName);
274                 }
275             });
276         }
277     }
278 
279     private class OnNetworkUpdateListener implements
280             WifiConfigManager.OnNetworkUpdateListener {
281         @Override
onConnectChoiceSet(@onNull List<WifiConfiguration> networks, String choiceKey, int rssi)282         public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks,
283                 String choiceKey, int rssi) {
284             onUserConnectChoiceSet(networks, choiceKey, rssi);
285         }
286         @Override
onConnectChoiceRemoved(String choiceKey)287         public void onConnectChoiceRemoved(String choiceKey) {
288             onUserConnectChoiceRemove(choiceKey);
289         }
290 
291     }
292 
onUserConnectChoiceRemove(String choiceKey)293     private void onUserConnectChoiceRemove(String choiceKey) {
294         mProviders.values().stream()
295                 .filter(provider -> TextUtils.equals(provider.getConnectChoice(), choiceKey))
296                 .forEach(provider -> {
297                     provider.setUserConnectChoice(null, 0);
298                 });
299         mWifiConfigManager.saveToStore(true);
300     }
301 
onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey, int rssi)302     private void onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey,
303             int rssi) {
304         for (WifiConfiguration config : networks) {
305             PasspointProvider provider = mProviders.get(config.getProfileKey());
306             if (provider != null) {
307                 provider.setUserConnectChoice(choiceKey, rssi);
308             }
309         }
310         PasspointProvider provider = mProviders.get(choiceKey);
311         if (provider != null) {
312             provider.setUserConnectChoice(null, 0);
313         }
314         mWifiConfigManager.saveToStore(true);
315     }
316 
317     /**
318      * Remove all Passpoint profiles installed by the app that has been disabled or uninstalled.
319      *
320      * @param packageName Package name of the app to remove the corresponding Passpoint profiles.
321      */
removePasspointProviderWithPackage(@onNull String packageName)322     public void removePasspointProviderWithPackage(@NonNull String packageName) {
323         stopTrackingAppOpsChange(packageName);
324         for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage(
325                 packageName).entrySet()) {
326             String uniqueId = entry.getValue().getConfig().getUniqueId();
327             removeProvider(Process.WIFI_UID /* ignored */, true, uniqueId, null);
328         }
329     }
330 
getPasspointProviderWithPackage( @onNull String packageName)331     private Map<String, PasspointProvider> getPasspointProviderWithPackage(
332             @NonNull String packageName) {
333         return mProviders.entrySet().stream().filter(
334                 entry -> TextUtils.equals(packageName,
335                         entry.getValue().getPackageName())).collect(
336                 Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
337     }
338 
startTrackingAppOpsChange(@onNull String packageName, int uid)339     private void startTrackingAppOpsChange(@NonNull String packageName, int uid) {
340         // The package is already registered.
341         if (mAppOpsChangedListenerPerApp.containsKey(packageName)) return;
342         AppOpsChangedListener appOpsChangedListener = new AppOpsChangedListener(packageName, uid);
343         mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener);
344         mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener);
345     }
346 
stopTrackingAppOpsChange(@onNull String packageName)347     private void stopTrackingAppOpsChange(@NonNull String packageName) {
348         AppOpsChangedListener appOpsChangedListener = mAppOpsChangedListenerPerApp.remove(
349                 packageName);
350         if (appOpsChangedListener == null) {
351             Log.i(TAG, "No app ops listener found for " + packageName);
352             return;
353         }
354         mAppOps.stopWatchingMode(appOpsChangedListener);
355     }
356 
PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiSettingsStore wifiSettingsStore, WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, MacAddressUtil macAddressUtil, WifiPermissionsUtil wifiPermissionsUtil)357     public PasspointManager(Context context, WifiInjector wifiInjector, Handler handler,
358             WifiNative wifiNative, WifiKeyStore keyStore, Clock clock,
359             PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager,
360             WifiConfigStore wifiConfigStore,
361             WifiSettingsStore wifiSettingsStore,
362             WifiMetrics wifiMetrics,
363             WifiCarrierInfoManager wifiCarrierInfoManager,
364             MacAddressUtil macAddressUtil,
365             WifiPermissionsUtil wifiPermissionsUtil) {
366         mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiInjector,
367                 new CallbackHandler(context));
368         mWifiInjector = wifiInjector;
369         mHandler = handler;
370         mKeyStore = keyStore;
371         mObjectFactory = objectFactory;
372         mProviders = new HashMap<>();
373         mAnqpCache = objectFactory.makeAnqpCache(clock);
374         mAnqpRequestManager = objectFactory.makeANQPRequestManager(mPasspointEventHandler, clock);
375         mWifiConfigManager = wifiConfigManager;
376         mWifiMetrics = wifiMetrics;
377         mProviderIndex = 0;
378         mWifiCarrierInfoManager = wifiCarrierInfoManager;
379         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigUserStoreData(
380                 mKeyStore, mWifiCarrierInfoManager, new UserDataSourceHandler(), clock));
381         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigSharedStoreData(
382                 new SharedDataSourceHandler()));
383         mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, wifiNative,
384                 this, wifiMetrics);
385         ActivityManager activityManager = context.getSystemService(ActivityManager.class);
386         mIsLowMemory = activityManager.isLowRamDevice();
387         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
388         sPasspointManager = this;
389         mMacAddressUtil = macAddressUtil;
390         mClock = clock;
391         mHandler.postAtFrontOfQueue(() ->
392                 mWifiConfigManager.addOnNetworkUpdateListener(
393                         new PasspointManager.OnNetworkUpdateListener()));
394         mWifiPermissionsUtil = wifiPermissionsUtil;
395         mSettingsStore = wifiSettingsStore;
396         mEnabled = mSettingsStore.isWifiPasspointEnabled();
397     }
398 
399     /**
400      * Initializes the provisioning flow with a looper.
401      * This looper should be tied to a background worker thread since PasspointProvisioner has a
402      * heavy workload.
403      */
initializeProvisioner(Looper looper)404     public void initializeProvisioner(Looper looper) {
405         mPasspointProvisioner.init(looper);
406     }
407 
408     /**
409      * Sets the {@link PasspointNetworkNominateHelper} used by this PasspointManager.
410      */
setPasspointNetworkNominateHelper( @ullable PasspointNetworkNominateHelper nominateHelper)411     public void setPasspointNetworkNominateHelper(
412             @Nullable PasspointNetworkNominateHelper nominateHelper) {
413         mPasspointNetworkNominateHelper = nominateHelper;
414     }
415 
416     /**
417      * Enable verbose logging
418      * @param verbose enables verbose logging
419      */
enableVerboseLogging(boolean verbose)420     public void enableVerboseLogging(boolean verbose) {
421         mVerboseLoggingEnabled = verbose;
422         mPasspointProvisioner.enableVerboseLogging(verbose);
423         for (PasspointProvider provider : mProviders.values()) {
424             provider.enableVerboseLogging(verbose);
425         }
426     }
427 
updateWifiConfigInWcmIfPresent( WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion)428     private void updateWifiConfigInWcmIfPresent(
429             WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion) {
430         WifiConfiguration configInWcm =
431                 mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey());
432         if (configInWcm == null) return;
433         // suggestion != saved
434         if (isFromSuggestion != configInWcm.fromWifiNetworkSuggestion) return;
435         // is suggestion from same app.
436         if (isFromSuggestion
437                 && (configInWcm.creatorUid != uid
438                 || !TextUtils.equals(configInWcm.creatorName, packageName))) {
439             return;
440         }
441         NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
442                 newConfig, uid, packageName, false);
443         if (!result.isSuccess()) {
444             Log.e(TAG, "Failed to update config in WifiConfigManager");
445         } else {
446             mWifiConfigManager.allowAutojoin(result.getNetworkId(), newConfig.allowAutojoin);
447             if (mVerboseLoggingEnabled) {
448                 Log.v(TAG, "Updated config in WifiConfigManager");
449             }
450         }
451     }
452 
453     /**
454      * Add or update a Passpoint provider with the given configuration.
455      *
456      * Each provider is uniquely identified by its unique identifier, see
457      * {@link PasspointConfiguration#getUniqueId()}.
458      * In the case when there is an existing configuration with the same unique identifier,
459      * a provider with the new configuration will replace the existing provider.
460      *
461      * @param config Configuration of the Passpoint provider to be added
462      * @param uid Uid of the app adding/Updating {@code config}
463      * @param packageName Package name of the app adding/Updating {@code config}
464      * @param isFromSuggestion Whether this {@code config} is from suggestion API
465      * @param isTrusted Whether this {@code config} an trusted network, default should be true.
466      *                  Only able set to false when {@code isFromSuggestion} is true, otherwise
467      *                  adding {@code config} will be false.
468      * @return true if provider is added, false otherwise
469      */
addOrUpdateProvider(PasspointConfiguration config, int uid, String packageName, boolean isFromSuggestion, boolean isTrusted)470     public boolean addOrUpdateProvider(PasspointConfiguration config, int uid,
471             String packageName, boolean isFromSuggestion, boolean isTrusted) {
472         mWifiMetrics.incrementNumPasspointProviderInstallation();
473         if (config == null) {
474             Log.e(TAG, "Configuration not provided");
475             return false;
476         }
477         if (!config.validate()) {
478             Log.e(TAG, "Invalid configuration");
479             return false;
480         }
481         if (!(isFromSuggestion || isTrusted)) {
482             Log.e(TAG, "Set isTrusted to false on a non suggestion passpoint is not allowed");
483             return false;
484         }
485         if (config.getServiceFriendlyNames() != null && isFromSuggestion) {
486             Log.e(TAG, "Passpoint from suggestion should not have ServiceFriendlyNames");
487             return false;
488         }
489         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) {
490             Log.e(TAG, "UID " + uid + " not visible to the current user");
491             return false;
492         }
493         if (getPasspointProviderWithPackage(packageName).size()
494                 >= WifiManager.getMaxNumberOfNetworkSuggestionsPerApp(mIsLowMemory)) {
495             Log.e(TAG, "packageName " + packageName + " has too many passpoint with exceed the "
496                     + "limitation");
497             return false;
498         }
499 
500         mWifiCarrierInfoManager.tryUpdateCarrierIdForPasspoint(config);
501         // Create a provider and install the necessary certificates and keys.
502         PasspointProvider newProvider = mObjectFactory.makePasspointProvider(config, mKeyStore,
503                 mWifiCarrierInfoManager, mProviderIndex++, uid, packageName, isFromSuggestion,
504                 mClock);
505         newProvider.setTrusted(isTrusted);
506 
507         boolean metricsNoRootCa = false;
508         boolean metricsSelfSignedRootCa = false;
509         boolean metricsSubscriptionExpiration = false;
510 
511         if (config.getCredential().getUserCredential() != null
512                 || config.getCredential().getCertCredential() != null) {
513             X509Certificate[] x509Certificates = config.getCredential().getCaCertificates();
514             if (x509Certificates == null) {
515                 metricsNoRootCa = true;
516             } else {
517                 try {
518                     for (X509Certificate certificate : x509Certificates) {
519                         verifyCaCert(certificate);
520                     }
521                 } catch (CertPathValidatorException e) {
522                     // A self signed Root CA will fail path validation checks with NO_TRUST_ANCHOR
523                     if (e.getReason() == NO_TRUST_ANCHOR) {
524                         metricsSelfSignedRootCa = true;
525                     }
526                 } catch (Exception e) {
527                     // Other exceptions, fall through, will be handled below
528                 }
529             }
530         }
531         if (config.getSubscriptionExpirationTimeMillis() != Long.MIN_VALUE) {
532             metricsSubscriptionExpiration = true;
533         }
534 
535         if (!newProvider.installCertsAndKeys()) {
536             Log.e(TAG, "Failed to install certificates and keys to keystore");
537             return false;
538         }
539 
540         // Remove existing provider with the same unique ID.
541         if (mProviders.containsKey(config.getUniqueId())) {
542             PasspointProvider old = mProviders.get(config.getUniqueId());
543             // If new profile is from suggestion and from a different App, ignore new profile,
544             // return false.
545             // If from same app, update it.
546             if (isFromSuggestion && !old.getPackageName().equals(packageName)) {
547                 newProvider.uninstallCertsAndKeys();
548                 return false;
549             }
550             Log.d(TAG, "Replacing configuration for FQDN: " + config.getHomeSp().getFqdn()
551                     + " and unique ID: " + config.getUniqueId());
552             old.uninstallCertsAndKeys();
553             mProviders.remove(config.getUniqueId());
554             // Keep the user connect choice and AnonymousIdentity
555             newProvider.setUserConnectChoice(old.getConnectChoice(), old.getConnectChoiceRssi());
556             newProvider.setAnonymousIdentity(old.getAnonymousIdentity());
557             // New profile changes the credential, remove the related WifiConfig.
558             if (!old.equals(newProvider)) {
559                 mWifiConfigManager.removePasspointConfiguredNetwork(
560                         newProvider.getWifiConfig().getProfileKey());
561             } else {
562                 // If there is a config cached in WifiConfigManager, update it with new info.
563                 updateWifiConfigInWcmIfPresent(
564                         newProvider.getWifiConfig(), uid, packageName, isFromSuggestion);
565             }
566         }
567         newProvider.enableVerboseLogging(mVerboseLoggingEnabled);
568         mProviders.put(config.getUniqueId(), newProvider);
569         if (!isFromSuggestion) {
570             // Suggestions will be handled by the WifiNetworkSuggestionsManager
571             mWifiConfigManager.saveToStore(true /* forceWrite */);
572         }
573         if (!isFromSuggestion && newProvider.getPackageName() != null) {
574             startTrackingAppOpsChange(newProvider.getPackageName(), uid);
575         }
576         Log.d(TAG, "Added/updated Passpoint configuration for FQDN: "
577                 + config.getHomeSp().getFqdn() + " with unique ID: " + config.getUniqueId()
578                 + " by UID: " + uid);
579         if (metricsNoRootCa) {
580             mWifiMetrics.incrementNumPasspointProviderWithNoRootCa();
581         }
582         if (metricsSelfSignedRootCa) {
583             mWifiMetrics.incrementNumPasspointProviderWithSelfSignedRootCa();
584         }
585         if (metricsSubscriptionExpiration) {
586             mWifiMetrics.incrementNumPasspointProviderWithSubscriptionExpiration();
587         }
588         if (SdkLevel.isAtLeastS() && config.getDecoratedIdentityPrefix() != null) {
589             mWifiMetrics.incrementTotalNumberOfPasspointProfilesWithDecoratedIdentity();
590         }
591         mWifiMetrics.incrementNumPasspointProviderInstallSuccess();
592         if (mPasspointNetworkNominateHelper != null) {
593             mPasspointNetworkNominateHelper.updateBestMatchScanDetailForProviders();
594         }
595         return true;
596     }
597 
removeProviderInternal(PasspointProvider provider, int callingUid, boolean privileged)598     private boolean removeProviderInternal(PasspointProvider provider, int callingUid,
599             boolean privileged) {
600         if (!privileged && callingUid != provider.getCreatorUid()) {
601             Log.e(TAG, "UID " + callingUid + " cannot remove profile created by "
602                     + provider.getCreatorUid());
603             return false;
604         }
605         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(callingUid)) {
606             Log.e(TAG, "UID " + callingUid + " not visible to the current user");
607             return false;
608         }
609         provider.uninstallCertsAndKeys();
610         String packageName = provider.getPackageName();
611         if (!provider.isFromSuggestion()) {
612             // Remove non-suggestion configs corresponding to the profile in WifiConfigManager.
613             // Suggestion passpoint will be handled by WifiNetworkSuggestionsManager
614             mWifiConfigManager.removePasspointConfiguredNetwork(
615                     provider.getWifiConfig().getProfileKey());
616         }
617         String uniqueId = provider.getConfig().getUniqueId();
618         mProviders.remove(uniqueId);
619         mWifiConfigManager.removeConnectChoiceFromAllNetworks(uniqueId);
620         if (!provider.isFromSuggestion()) {
621             // Suggestions will be handled by the WifiNetworkSuggestionsManager
622             mWifiConfigManager.saveToStore(true /* forceWrite */);
623         }
624 
625         // Stop monitoring the package if there is no Passpoint profile installed by the package
626         if (mAppOpsChangedListenerPerApp.containsKey(packageName)
627                 && getPasspointProviderWithPackage(packageName).size() == 0) {
628             stopTrackingAppOpsChange(packageName);
629         }
630         Log.d(TAG, "Removed Passpoint configuration: " + uniqueId);
631         mWifiMetrics.incrementNumPasspointProviderUninstallSuccess();
632         return true;
633     }
634 
635     /**
636      * Remove a Passpoint provider identified by the given its unique identifier.
637      *
638      * @param callingUid Calling UID.
639      * @param privileged Whether the caller is a privileged entity
640      * @param uniqueId The ID of the provider to remove. Not required if FQDN is specified.
641      * @param fqdn The FQDN of the provider to remove. Not required if unique ID is specified.
642      * @return true if a provider is removed, false otherwise
643      */
removeProvider(int callingUid, boolean privileged, String uniqueId, String fqdn)644     public boolean removeProvider(int callingUid, boolean privileged, String uniqueId,
645             String fqdn) {
646         if (uniqueId == null && fqdn == null) {
647             mWifiMetrics.incrementNumPasspointProviderUninstallation();
648             Log.e(TAG, "Cannot remove provider, both FQDN and unique ID are null");
649             return false;
650         }
651 
652         if (uniqueId != null) {
653             // Unique identifier provided
654             mWifiMetrics.incrementNumPasspointProviderUninstallation();
655             PasspointProvider provider = mProviders.get(uniqueId);
656             if (provider == null) {
657                 Log.e(TAG, "Config doesn't exist");
658                 return false;
659             }
660             return removeProviderInternal(provider, callingUid, privileged);
661         }
662 
663         // FQDN provided, loop through all profiles with matching FQDN
664         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
665         int removedProviders = 0;
666         int numOfUninstallations = 0;
667         for (PasspointProvider provider : passpointProviders) {
668             if (!TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
669                 continue;
670             }
671             mWifiMetrics.incrementNumPasspointProviderUninstallation();
672             numOfUninstallations++;
673             if (removeProviderInternal(provider, callingUid, privileged)) {
674                 removedProviders++;
675             }
676         }
677 
678         if (numOfUninstallations == 0) {
679             // Update uninstallation requests metrics here to cover the corner case of trying to
680             // uninstall a non-existent provider.
681             mWifiMetrics.incrementNumPasspointProviderUninstallation();
682         }
683 
684         return removedProviders > 0;
685     }
686 
687     /**
688      * Enable or disable the auto-join configuration. Auto-join controls whether or not the
689      * passpoint configuration is used for auto connection (network selection). Note that even
690      * when auto-join is disabled the configuration can still be used for manual connection.
691      *
692      * @param uniqueId The unique identifier of the configuration. Not required if FQDN is specified
693      * @param fqdn The FQDN of the configuration. Not required if uniqueId is specified.
694      * @param enableAutojoin true to enable auto-join, false to disable.
695      * @return true on success, false otherwise (e.g. if no such provider exists).
696      */
enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin)697     public boolean enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin) {
698         if (uniqueId == null && fqdn == null) {
699             return false;
700         }
701         if (uniqueId != null) {
702             // Unique identifier provided
703             PasspointProvider provider = mProviders.get(uniqueId);
704             if (provider == null) {
705                 Log.e(TAG, "Config doesn't exist");
706                 return false;
707             }
708             if (provider.setAutojoinEnabled(enableAutojoin)) {
709                 mWifiMetrics.logUserActionEvent(enableAutojoin
710                                 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
711                                 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
712                         provider.isFromSuggestion(), true);
713                 // Update WifiConfigManager if changed.
714                 updateWifiConfigInWcmIfPresent(provider.getWifiConfig(), provider.getCreatorUid(),
715                         provider.getPackageName(), provider.isFromSuggestion());
716             }
717 
718             mWifiConfigManager.saveToStore(true);
719             return true;
720         }
721 
722         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
723         boolean found = false;
724 
725         // FQDN provided, loop through all profiles with matching FQDN
726         for (PasspointProvider provider : passpointProviders) {
727             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
728                 if (provider.setAutojoinEnabled(enableAutojoin)) {
729                     mWifiMetrics.logUserActionEvent(enableAutojoin
730                                     ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON
731                                     : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF,
732                             provider.isFromSuggestion(), true);
733                     // Update WifiConfigManager if changed.
734                     updateWifiConfigInWcmIfPresent(provider.getWifiConfig(),
735                             provider.getCreatorUid(), provider.getPackageName(),
736                             provider.isFromSuggestion());
737                 }
738                 found = true;
739             }
740         }
741         if (found) {
742             mWifiConfigManager.saveToStore(true);
743         }
744         return found;
745     }
746 
747     /**
748      * Enable or disable MAC randomization for this passpoint profile.
749      * @param fqdn The FQDN of the configuration
750      * @param enable true to enable MAC randomization, false to disable
751      * @return true on success, false otherwise (e.g. if no such provider exists).
752      */
enableMacRandomization(@onNull String fqdn, boolean enable)753     public boolean enableMacRandomization(@NonNull String fqdn, boolean enable) {
754         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
755         boolean found = false;
756 
757         // Loop through all profiles with matching FQDN
758         for (PasspointProvider provider : passpointProviders) {
759             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
760                 boolean settingChanged = provider.setMacRandomizationEnabled(enable);
761                 if (settingChanged) {
762                     mWifiMetrics.logUserActionEvent(enable
763                                     ? UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON
764                                     : UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF,
765                             provider.isFromSuggestion(), true);
766                     mWifiConfigManager.removePasspointConfiguredNetwork(
767                             provider.getWifiConfig().getProfileKey());
768                 }
769                 found = true;
770             }
771         }
772         if (found) {
773             mWifiConfigManager.saveToStore(true);
774         }
775         return found;
776     }
777 
778     /**
779      * Set the metered override value for this passpoint profile
780      * @param fqdn The FQDN of the configuration
781      * @param meteredOverride One of the values in {@link MeteredOverride}
782      * @return true on success, false otherwise (e.g. if no such provider exists).
783      */
setMeteredOverride(@onNull String fqdn, @MeteredOverride int meteredOverride)784     public boolean setMeteredOverride(@NonNull String fqdn, @MeteredOverride int meteredOverride) {
785         ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values());
786         boolean found = false;
787 
788         // Loop through all profiles with matching FQDN
789         for (PasspointProvider provider : passpointProviders) {
790             if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) {
791                 if (provider.setMeteredOverride(meteredOverride)) {
792                     mWifiMetrics.logUserActionEvent(
793                             WifiMetrics.convertMeteredOverrideEnumToUserActionEventType(
794                                     meteredOverride),
795                             provider.isFromSuggestion(), true);
796                 }
797                 found = true;
798             }
799         }
800         if (found) {
801             mWifiConfigManager.saveToStore(true);
802         }
803         return found;
804     }
805 
806     /**
807      * Return the installed Passpoint provider configurations.
808      * An empty list will be returned when no provider is installed.
809      *
810      * @param callingUid Calling UID.
811      * @param privileged Whether the caller is a privileged entity
812      * @return A list of {@link PasspointConfiguration}
813      */
getProviderConfigs(int callingUid, boolean privileged)814     public List<PasspointConfiguration> getProviderConfigs(int callingUid,
815             boolean privileged) {
816         List<PasspointConfiguration> configs = new ArrayList<>();
817         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
818             PasspointProvider provider = entry.getValue();
819             if (privileged || callingUid == provider.getCreatorUid()) {
820                 if (provider.isFromSuggestion()) {
821                     continue;
822                 }
823                 configs.add(provider.getConfig());
824             }
825         }
826         return configs;
827     }
828 
829     /**
830      * Find all providers that can provide service through the given AP, which means the
831      * providers contained credential to authenticate with the given AP.
832      *
833      * If there is any home provider available, will return a list of matched home providers.
834      * Otherwise will return a list of matched roaming providers.
835      *
836      * A empty list will be returned if no matching is found.
837      *
838      * @param scanResult The scan result associated with the AP
839      * @return a list of pairs of {@link PasspointProvider} and match status.
840      */
matchProvider( ScanResult scanResult)841     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider(
842             ScanResult scanResult) {
843         return matchProvider(scanResult, true);
844     }
845 
846     /**
847      * Find all providers that can provide service through the given AP, which means the
848      * providers contained credential to authenticate with the given AP.
849      *
850      * A empty list will be returned if no matching is found.
851      *
852      * @param scanResult The scan result associated with the AP
853      * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty
854      * @return a list of pairs of {@link PasspointProvider} and match status.
855      */
matchProvider( ScanResult scanResult, boolean anqpRequestAllowed)856     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider(
857             ScanResult scanResult, boolean anqpRequestAllowed) {
858         if (!mEnabled) {
859             return Collections.emptyList();
860         }
861         List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders(
862                 scanResult, anqpRequestAllowed).stream()
863                 .filter(a -> !isExpired(a.first.getConfig()))
864                 .collect(Collectors.toList());
865         if (allMatches.isEmpty()) {
866             if (mVerboseLoggingEnabled) {
867                 Log.d(TAG, "No service provider found for " + scanResult.SSID);
868             }
869         }
870         return allMatches;
871     }
872 
873     /**
874      * Return a list of all providers that can provide service through the given AP.
875      *
876      * @param scanResult The scan result associated with the AP
877      * @return a list of pairs of {@link PasspointProvider} and match status.
878      */
getAllMatchedProviders( ScanResult scanResult)879     public @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
880             ScanResult scanResult) {
881         return getAllMatchedProviders(scanResult, true);
882     }
883 
884     /**
885      * Return a list of all providers that can provide service through the given AP.
886      *
887      * @param scanResult The scan result associated with the AP
888      * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty
889      * @return a list of pairs of {@link PasspointProvider} and match status.
890      */
getAllMatchedProviders( ScanResult scanResult, boolean anqpRequestAllowed)891     private @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
892             ScanResult scanResult, boolean anqpRequestAllowed) {
893         List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>();
894 
895         // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
896         // Vendor Specific IE.
897         InformationElementUtil.RoamingConsortium roamingConsortium =
898                 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
899         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
900                 scanResult.informationElements);
901 
902         // Lookup ANQP data in the cache.
903         long bssid;
904         try {
905             bssid = Utils.parseMac(scanResult.BSSID);
906         } catch (IllegalArgumentException e) {
907             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
908             return allMatches;
909         }
910         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
911                 vsa.anqpDomainID);
912         ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
913         if (anqpEntry == null) {
914             if (anqpRequestAllowed) {
915                 mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
916                         roamingConsortium.anqpOICount > 0, vsa.hsRelease);
917             }
918             Log.d(TAG, "ANQP entry not found for: " + anqpKey);
919             return allMatches;
920         }
921         boolean anyProviderUpdated = false;
922         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
923             PasspointProvider provider = entry.getValue();
924             if (provider.tryUpdateCarrierId()) {
925                 anyProviderUpdated = true;
926             }
927             if (mVerboseLoggingEnabled) {
928                 Log.d(TAG, "Matching provider " + provider.getConfig().getHomeSp().getFqdn()
929                         + " with "
930                         + anqpEntry.getElements().get(Constants.ANQPElementType.ANQPDomName));
931             }
932             PasspointMatch matchStatus = provider.match(anqpEntry.getElements(),
933                     roamingConsortium, scanResult);
934             if (matchStatus == PasspointMatch.HomeProvider
935                     || matchStatus == PasspointMatch.RoamingProvider) {
936                 allMatches.add(Pair.create(provider, matchStatus));
937             }
938         }
939         if (anyProviderUpdated) {
940             mWifiConfigManager.saveToStore(true);
941         }
942         if (allMatches.size() != 0) {
943             for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
944                 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
945                         match.first.getConfig().getHomeSp().getFqdn(),
946                         match.second == PasspointMatch.HomeProvider ? "Home Provider"
947                                 : "Roaming Provider"));
948             }
949         } else {
950             if (mVerboseLoggingEnabled) {
951                 Log.d(TAG, "No service providers found for " + scanResult.SSID);
952             }
953         }
954         return allMatches;
955     }
956 
957     /**
958      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the
959      * current {@link PasspointManager}.
960      *
961      * This will not trigger a config store write, since this will be invoked as part of the
962      * configuration migration, the caller will be responsible for triggering store write
963      * after the migration is completed.
964      *
965      * @param config {@link WifiConfiguration} representation of the Passpoint configuration
966      * @return true on success
967      */
addLegacyPasspointConfig(WifiConfiguration config)968     public static boolean addLegacyPasspointConfig(WifiConfiguration config) {
969         if (sPasspointManager == null) {
970             Log.e(TAG, "PasspointManager have not been initialized yet");
971             return false;
972         }
973         Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN);
974         return sPasspointManager.addWifiConfig(config);
975     }
976 
977     /**
978      * Sweep the ANQP cache to remove expired entries.
979      */
sweepCache()980     public void sweepCache() {
981         mAnqpCache.sweep();
982     }
983 
984     /**
985      * Notify the completion of an ANQP request.
986      * TODO(zqiu): currently the notification is done through WifiMonitor,
987      * will no longer be the case once we switch over to use wificond.
988      */
notifyANQPDone(AnqpEvent anqpEvent)989     public void notifyANQPDone(AnqpEvent anqpEvent) {
990         mPasspointEventHandler.notifyANQPDone(anqpEvent);
991     }
992 
993     /**
994      * Notify the completion of an icon request.
995      * TODO(zqiu): currently the notification is done through WifiMonitor,
996      * will no longer be the case once we switch over to use wificond.
997      */
notifyIconDone(IconEvent iconEvent)998     public void notifyIconDone(IconEvent iconEvent) {
999         mPasspointEventHandler.notifyIconDone(iconEvent);
1000     }
1001 
1002     /**
1003      * Notify the reception of a Wireless Network Management (WNM) frame.
1004      */
receivedWnmFrame(WnmData data)1005     public void receivedWnmFrame(WnmData data) {
1006         mPasspointEventHandler.notifyWnmFrameReceived(data);
1007     }
1008 
1009     /**
1010      * Request the specified icon file |fileName| from the specified AP |bssid|.
1011      * @return true if the request is sent successfully, false otherwise
1012      */
queryPasspointIcon(long bssid, String fileName)1013     public boolean queryPasspointIcon(long bssid, String fileName) {
1014         return mPasspointEventHandler.requestIcon(bssid, fileName);
1015     }
1016 
1017     /**
1018      * Lookup the ANQP elements associated with the given AP from the cache. An empty map
1019      * will be returned if no match found in the cache.
1020      *
1021      * @param scanResult The scan result associated with the AP
1022      * @return Map of ANQP elements
1023      */
getANQPElements(ScanResult scanResult)1024     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) {
1025         // Retrieve the Hotspot 2.0 Vendor Specific IE.
1026         InformationElementUtil.Vsa vsa =
1027                 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements);
1028 
1029         // Lookup ANQP data in the cache.
1030         long bssid;
1031         try {
1032             bssid = Utils.parseMac(scanResult.BSSID);
1033         } catch (IllegalArgumentException e) {
1034             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
1035             return new HashMap<>();
1036         }
1037         ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey(
1038                 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID));
1039         if (anqpEntry != null) {
1040             return anqpEntry.getElements();
1041         }
1042         return new HashMap<>();
1043     }
1044 
1045     /**
1046      * Return a map of all matching configurations keys with corresponding scanResults (or an empty
1047      * map if none).
1048      *
1049      * @param scanResults The list of scan results
1050      * @return Map that consists of identifies and corresponding scanResults per network type
1051      * ({@link WifiManager#PASSPOINT_HOME_NETWORK}, {@link WifiManager#PASSPOINT_ROAMING_NETWORK}).
1052      */
1053     public Map<String, Map<Integer, List<ScanResult>>>
getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults)1054             getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults) {
1055         if (scanResults == null) {
1056             Log.e(TAG, "Attempt to get matching config for a null ScanResults");
1057             return new HashMap<>();
1058         }
1059         Map<String, Map<Integer, List<ScanResult>>> configs = new HashMap<>();
1060 
1061         for (ScanResult scanResult : scanResults) {
1062             if (!scanResult.isPasspointNetwork()) continue;
1063             List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders(
1064                     scanResult);
1065             for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) {
1066                 WifiConfiguration config = matchedProvider.first.getWifiConfig();
1067                 int type = WifiManager.PASSPOINT_HOME_NETWORK;
1068                 if (!config.isHomeProviderNetwork) {
1069                     type = WifiManager.PASSPOINT_ROAMING_NETWORK;
1070                 }
1071                 Map<Integer, List<ScanResult>> scanResultsPerNetworkType =
1072                         configs.computeIfAbsent(config.getProfileKey(),
1073                                 k -> new HashMap<>());
1074                 List<ScanResult> matchingScanResults = scanResultsPerNetworkType.computeIfAbsent(
1075                         type, k -> new ArrayList<>());
1076                 matchingScanResults.add(scanResult);
1077             }
1078         }
1079 
1080         return configs;
1081     }
1082 
1083     /**
1084      * Returns the list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given list
1085      * of ScanResult.
1086      *
1087      * An empty map will be returned when an invalid scanResults are provided or no match is found.
1088      *
1089      * @param scanResults a list of ScanResult that has Passpoint APs.
1090      * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult}
1091      */
getMatchingOsuProviders( List<ScanResult> scanResults)1092     public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders(
1093             List<ScanResult> scanResults) {
1094         if (scanResults == null) {
1095             Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult");
1096             return new HashMap();
1097         }
1098 
1099         Map<OsuProvider, List<ScanResult>> osuProviders = new HashMap<>();
1100         for (ScanResult scanResult : scanResults) {
1101             if (!scanResult.isPasspointNetwork()) continue;
1102 
1103             // Lookup OSU Providers ANQP element.
1104             Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult);
1105             if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
1106                 continue;
1107             }
1108             HSOsuProvidersElement element =
1109                     (HSOsuProvidersElement) anqpElements.get(
1110                             Constants.ANQPElementType.HSOSUProviders);
1111             for (OsuProviderInfo info : element.getProviders()) {
1112                 // Set null for OSU-SSID in the class because OSU-SSID is a factor for hotspot
1113                 // operator rather than service provider, which means it can be different for
1114                 // each hotspot operators.
1115                 OsuProvider provider = new OsuProvider((WifiSsid) null, info.getFriendlyNames(),
1116                         info.getServiceDescription(), info.getServerUri(),
1117                         info.getNetworkAccessIdentifier(), info.getMethodList());
1118                 List<ScanResult> matchingScanResults = osuProviders.get(provider);
1119                 if (matchingScanResults == null) {
1120                     matchingScanResults = new ArrayList<>();
1121                     osuProviders.put(provider, matchingScanResults);
1122                 }
1123                 matchingScanResults.add(scanResult);
1124             }
1125         }
1126         return osuProviders;
1127     }
1128 
1129     /**
1130      * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) providers
1131      *
1132      * An empty map will be returned when an invalid {@code osuProviders} are provided or no match
1133      * is found.
1134      *
1135      * @param osuProviders a list of {@link OsuProvider}
1136      * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
1137      */
getMatchingPasspointConfigsForOsuProviders( List<OsuProvider> osuProviders)1138     public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
1139             List<OsuProvider> osuProviders) {
1140         Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>();
1141 
1142         for (OsuProvider osuProvider : osuProviders) {
1143             Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList();
1144             if (friendlyNamesForOsuProvider == null) continue;
1145             for (PasspointProvider provider : mProviders.values()) {
1146                 PasspointConfiguration passpointConfiguration = provider.getConfig();
1147                 Map<String, String> serviceFriendlyNamesForPpsMo =
1148                         passpointConfiguration.getServiceFriendlyNames();
1149                 if (serviceFriendlyNamesForPpsMo == null) continue;
1150 
1151                 for (Map.Entry<String, String> entry : serviceFriendlyNamesForPpsMo.entrySet()) {
1152                     String lang = entry.getKey();
1153                     String friendlyName = entry.getValue();
1154                     if (friendlyName == null) continue;
1155                     String osuFriendlyName = friendlyNamesForOsuProvider.get(lang);
1156                     if (osuFriendlyName == null) continue;
1157                     if (friendlyName.equals(osuFriendlyName)) {
1158                         matchingPasspointConfigs.put(osuProvider, passpointConfiguration);
1159                         break;
1160                     }
1161                 }
1162             }
1163         }
1164         return matchingPasspointConfigs;
1165     }
1166 
1167     /**
1168      * Returns the corresponding wifi configurations from {@link WifiConfigManager} for given a list
1169      * of Passpoint profile unique identifiers.
1170      *
1171      * Note: Not all matched Passpoint profile's WifiConfiguration will be returned, only the ones
1172      * already be added into the {@link WifiConfigManager} will be returned. As the returns of this
1173      * method is expected to show in Wifi Picker or use with
1174      * {@link WifiManager#connect(int, WifiManager.ActionListener)} API, each WifiConfiguration must
1175      * have a valid network Id.
1176      *
1177      * An empty list will be returned when no match is found.
1178      *
1179      * @param idList a list of unique identifiers
1180      * @return List of {@link WifiConfiguration} converted from {@link PasspointProvider}
1181      */
getWifiConfigsForPasspointProfiles(List<String> idList)1182     public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> idList) {
1183         if (mProviders.isEmpty()) {
1184             return Collections.emptyList();
1185         }
1186         List<WifiConfiguration> configs = new ArrayList<>();
1187         Set<String> uniqueIdSet = new HashSet<>();
1188         uniqueIdSet.addAll(idList);
1189         for (String uniqueId : uniqueIdSet) {
1190             PasspointProvider provider = mProviders.get(uniqueId);
1191             if (provider == null) {
1192                 continue;
1193             }
1194             WifiConfiguration config = provider.getWifiConfig();
1195             config = mWifiConfigManager.getConfiguredNetwork(config.getProfileKey());
1196             if (config == null) {
1197                 continue;
1198             }
1199             // If the Passpoint configuration is from a suggestion, check if the app shares this
1200             // suggestion with the user.
1201             if (provider.isFromSuggestion()
1202                     && !mWifiInjector.getWifiNetworkSuggestionsManager()
1203                     .isPasspointSuggestionSharedWithUser(config)) {
1204                 continue;
1205             }
1206             if (mWifiConfigManager.shouldUseNonPersistentRandomization(config)) {
1207                 config.setRandomizedMacAddress(MacAddress.fromString(DEFAULT_MAC_ADDRESS));
1208             } else {
1209                 MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(),
1210                         mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
1211                 if (result != null) {
1212                     config.setRandomizedMacAddress(result);
1213                 }
1214             }
1215             configs.add(config);
1216         }
1217         return configs;
1218     }
1219 
1220     /**
1221      * Invoked when a Passpoint network was successfully connected based on the credentials
1222      * provided by the given Passpoint provider
1223      *
1224      * @param uniqueId The unique identifier of the Passpointprofile
1225      */
onPasspointNetworkConnected(String uniqueId)1226     public void onPasspointNetworkConnected(String uniqueId) {
1227         PasspointProvider provider = mProviders.get(uniqueId);
1228         if (provider == null) {
1229             Log.e(TAG, "Passpoint network connected without provider: " + uniqueId);
1230             return;
1231         }
1232         if (!provider.getHasEverConnected()) {
1233             // First successful connection using this provider.
1234             provider.setHasEverConnected(true);
1235         }
1236     }
1237 
1238     /**
1239      * Update metrics related to installed Passpoint providers, this includes the number of
1240      * installed providers and the number of those providers that results in a successful network
1241      * connection.
1242      */
updateMetrics()1243     public void updateMetrics() {
1244         int numProviders = mProviders.size();
1245         int numConnectedProviders = 0;
1246         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
1247             if (entry.getValue().getHasEverConnected()) {
1248                 numConnectedProviders++;
1249             }
1250         }
1251         mWifiMetrics.updateSavedPasspointProfilesInfo(mProviders);
1252         mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders);
1253     }
1254 
1255     /**
1256      * Dump the current state of PasspointManager to the provided output stream.
1257      *
1258      * @param pw The output stream to write to
1259      */
dump(PrintWriter pw)1260     public void dump(PrintWriter pw) {
1261         pw.println("Dump of PasspointManager");
1262         pw.println("mEnabled: " + mEnabled);
1263         pw.println("PasspointManager - Providers Begin ---");
1264         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
1265             pw.println(entry.getValue());
1266         }
1267         pw.println("PasspointManager - Providers End ---");
1268         pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex);
1269         mAnqpCache.dump(pw);
1270         mAnqpRequestManager.dump(pw);
1271     }
1272 
1273     /**
1274      * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}.
1275      *
1276      * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration
1277      * @return true on success
1278      */
addWifiConfig(WifiConfiguration wifiConfig)1279     private boolean addWifiConfig(WifiConfiguration wifiConfig) {
1280         if (wifiConfig == null) {
1281             return false;
1282         }
1283 
1284         // Convert to PasspointConfiguration
1285         PasspointConfiguration passpointConfig =
1286                 PasspointProvider.convertFromWifiConfig(wifiConfig);
1287         if (passpointConfig == null) {
1288             return false;
1289         }
1290 
1291         // Setup aliases for enterprise certificates and key.
1292         WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
1293         String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias();
1294         String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias();
1295         if (passpointConfig.getCredential().getUserCredential() != null
1296                 && TextUtils.isEmpty(caCertificateAliasSuffix)) {
1297             Log.e(TAG, "Missing CA Certificate for user credential");
1298             return false;
1299         }
1300         if (passpointConfig.getCredential().getCertCredential() != null) {
1301             if (TextUtils.isEmpty(caCertificateAliasSuffix)) {
1302                 Log.e(TAG, "Missing CA certificate for Certificate credential");
1303                 return false;
1304             }
1305             if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) {
1306                 Log.e(TAG, "Missing client certificate and key for certificate credential");
1307                 return false;
1308             }
1309         }
1310 
1311         // Note that for legacy configuration, the alias for client private key is the same as the
1312         // alias for the client certificate.
1313         PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore,
1314                 mWifiCarrierInfoManager,
1315                 mProviderIndex++, wifiConfig.creatorUid, null, false,
1316                 Arrays.asList(enterpriseConfig.getCaCertificateAlias()),
1317                 enterpriseConfig.getClientCertificateAlias(), null, false, false, mClock);
1318         provider.enableVerboseLogging(mVerboseLoggingEnabled);
1319         mProviders.put(passpointConfig.getUniqueId(), provider);
1320         return true;
1321     }
1322 
1323     /**
1324      * Start the subscription provisioning flow with a provider.
1325      * @param callingUid integer indicating the uid of the caller
1326      * @param provider {@link OsuProvider} the provider to subscribe to
1327      * @param callback {@link IProvisioningCallback} callback to update status to the caller
1328      * @return boolean return value from the provisioning method
1329      */
startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)1330     public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
1331             IProvisioningCallback callback) {
1332         return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback);
1333     }
1334 
1335     /**
1336      * Check if a Passpoint configuration is expired
1337      *
1338      * @param config {@link PasspointConfiguration} Passpoint configuration
1339      * @return True if the configuration is expired, false if not or expiration is unset
1340      */
isExpired(@onNull PasspointConfiguration config)1341     private boolean isExpired(@NonNull PasspointConfiguration config) {
1342         long expirationTime = config.getSubscriptionExpirationTimeMillis();
1343 
1344         if (expirationTime != Long.MIN_VALUE) {
1345             long curTime = System.currentTimeMillis();
1346 
1347             // Check expiration and return true for expired profiles
1348             if (curTime >= expirationTime) {
1349                 Log.d(TAG, "Profile for " + config.getServiceFriendlyName() + " has expired, "
1350                         + "expiration time: " + expirationTime + ", current time: "
1351                         + curTime);
1352                 return true;
1353             }
1354         }
1355         return false;
1356     }
1357 
1358     /**
1359      * Get the filtered ScanResults which could be served by the {@link PasspointConfiguration}.
1360      * @param passpointConfiguration The instance of {@link PasspointConfiguration}
1361      * @param scanResults The list of {@link ScanResult}
1362      * @return The filtered ScanResults
1363      */
1364     @NonNull
getMatchingScanResults( @onNull PasspointConfiguration passpointConfiguration, @NonNull List<ScanResult> scanResults)1365     public List<ScanResult> getMatchingScanResults(
1366             @NonNull PasspointConfiguration passpointConfiguration,
1367             @NonNull List<ScanResult> scanResults) {
1368         PasspointProvider provider = mObjectFactory.makePasspointProvider(passpointConfiguration,
1369                 null, mWifiCarrierInfoManager, 0, 0, null, false, mClock);
1370         List<ScanResult> filteredScanResults = new ArrayList<>();
1371         for (ScanResult scanResult : scanResults) {
1372             PasspointMatch matchInfo = provider.match(getANQPElements(scanResult),
1373                     InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements),
1374                     scanResult);
1375             if (matchInfo == PasspointMatch.HomeProvider
1376                     || matchInfo == PasspointMatch.RoamingProvider) {
1377                 filteredScanResults.add(scanResult);
1378             }
1379         }
1380 
1381         return filteredScanResults;
1382     }
1383 
1384     /**
1385      * Check if the providers list is empty
1386      *
1387      * @return true if the providers list is empty, false otherwise
1388      */
isProvidersListEmpty()1389     public boolean isProvidersListEmpty() {
1390         return mProviders.isEmpty();
1391     }
1392 
1393     /**
1394      * Clear ANQP requests and flush ANQP Cache (for factory reset)
1395      */
clearAnqpRequestsAndFlushCache()1396     public void clearAnqpRequestsAndFlushCache() {
1397         mAnqpRequestManager.clear();
1398         mAnqpCache.flush();
1399         mProviders.values().stream().forEach(p -> p.clearProviderBlock());
1400     }
1401 
1402     private PKIXParameters mInjectedPKIXParameters;
1403     private boolean mUseInjectedPKIX = false;
1404 
1405 
1406     /**
1407      * Used to speedup unit test.
1408      */
1409     @VisibleForTesting
injectPKIXParameters(PKIXParameters params)1410     public void injectPKIXParameters(PKIXParameters params) {
1411         mInjectedPKIXParameters = params;
1412     }
1413 
1414     /**
1415      * Used to speedup unit test.
1416      */
1417     @VisibleForTesting
setUseInjectedPKIX(boolean value)1418     public void setUseInjectedPKIX(boolean value) {
1419         mUseInjectedPKIX = value;
1420     }
1421 
1422     /**
1423      * Verify that the given certificate is trusted by one of the pre-loaded public CAs in the
1424      * system key store.
1425      *
1426      * @param caCert The CA Certificate to verify
1427      * @throws CertPathValidatorException
1428      * @throws Exception
1429      */
verifyCaCert(X509Certificate caCert)1430     private void verifyCaCert(X509Certificate caCert)
1431             throws GeneralSecurityException, IOException {
1432         CertificateFactory factory = CertificateFactory.getInstance("X.509");
1433         CertPathValidator validator =
1434                 CertPathValidator.getInstance(CertPathValidator.getDefaultType());
1435         CertPath path = factory.generateCertPath(Arrays.asList(caCert));
1436         PKIXParameters params;
1437         if (mUseInjectedPKIX) {
1438             params = mInjectedPKIXParameters;
1439         } else {
1440             KeyStore ks = KeyStore.getInstance("AndroidCAStore");
1441             ks.load(null, null);
1442             params = new PKIXParameters(ks);
1443             params.setRevocationEnabled(false);
1444         }
1445         validator.validate(path, params);
1446     }
1447 
1448     /**
1449      * Request the Venue URL ANQP-element from the AP post connection
1450      *
1451      * @param scanResult Scan result associated to the requested AP
1452      */
requestVenueUrlAnqpElement(@onNull ScanResult scanResult)1453     public void requestVenueUrlAnqpElement(@NonNull ScanResult scanResult) {
1454         long bssid;
1455         try {
1456             bssid = Utils.parseMac(scanResult.BSSID);
1457         } catch (IllegalArgumentException e) {
1458             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
1459             return;
1460         }
1461         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
1462                 scanResult.informationElements);
1463         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
1464                 vsa.anqpDomainID);
1465         // TODO(haishalom@): Should we limit to R3 only? vsa.hsRelease > NetworkDetail.HSRelease.R2
1466         // I am seeing R2's that respond to Venue URL request, so may keep it this way.
1467         // APs that do not support this ANQP request simply ignore it.
1468         mAnqpRequestManager.requestVenueUrlAnqpElement(bssid, anqpKey);
1469     }
1470 
1471     /**
1472      * Get the Venue URL associated to the scan result, matched to the system language. If no
1473      * Venue URL matches the system language, then entry number one is returned, which is considered
1474      * to be the venue's default language.
1475      *
1476      * @param scanResult Scan result
1477      * @return The Venue URL associated to the scan result or null if not found
1478      */
1479     @Nullable
getVenueUrl(@onNull ScanResult scanResult)1480     public URL getVenueUrl(@NonNull ScanResult scanResult) {
1481         long bssid;
1482         try {
1483             bssid = Utils.parseMac(scanResult.BSSID);
1484         } catch (IllegalArgumentException e) {
1485             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
1486             return null;
1487         }
1488         InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
1489                 scanResult.informationElements);
1490         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
1491                 vsa.anqpDomainID);
1492         ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
1493         if (anqpEntry == null) {
1494             return null;
1495         }
1496         VenueUrlElement venueUrlElement = (VenueUrlElement)
1497                 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueUrl);
1498         if (venueUrlElement == null || venueUrlElement.getVenueUrls().isEmpty()) {
1499             return null; // No Venue URL
1500         }
1501         VenueNameElement venueNameElement = (VenueNameElement)
1502                 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueName);
1503         if (venueNameElement == null
1504                 || venueUrlElement.getVenueUrls().size() != venueNameElement.getNames().size()) {
1505             Log.w(TAG, "Venue name list size mismatches the Venue URL list size");
1506             return null; // No match between Venue names Venue URLs
1507         }
1508 
1509         // Find the Venue URL that matches the system language. Venue URLs are ordered by venue
1510         // names.
1511         Locale locale = Locale.getDefault();
1512         URL venueUrl = null;
1513         int index = 1;
1514         for (I18Name venueName : venueNameElement.getNames()) {
1515             if (venueName.getLanguage().equals(locale.getISO3Language())) {
1516                 venueUrl = venueUrlElement.getVenueUrls().get(index);
1517                 break;
1518             }
1519             index++;
1520         }
1521 
1522         // If no venue URL for the system language is available, use entry number one
1523         if (venueUrl == null) {
1524             venueUrl = venueUrlElement.getVenueUrls().get(1);
1525         }
1526 
1527         if (mVerboseLoggingEnabled) {
1528             Log.d(TAG, "Venue URL to display (language = " + locale.getDisplayLanguage()
1529                     + "): " + (venueUrl != null ? venueUrl : "None"));
1530         }
1531         return venueUrl;
1532     }
1533 
1534     /**
1535      * Handle Deauthentication Imminent WNM-Notification event
1536      *
1537      * @param event Deauthentication Imminent WNM-Notification data
1538      * @param config Configuration of the currently connected network
1539      */
handleDeauthImminentEvent(WnmData event, WifiConfiguration config)1540     public void handleDeauthImminentEvent(WnmData event, WifiConfiguration config) {
1541         if (event == null || config == null) {
1542             return;
1543         }
1544 
1545         blockProvider(config.getProfileKey(), event.getBssid(), event.isEss(),
1546                 event.getDelay());
1547         mWifiMetrics.incrementPasspointDeauthImminentScope(event.isEss());
1548     }
1549 
1550     /**
1551      * Block a specific provider from network selection
1552      *
1553      * @param passpointUniqueId The unique ID of the Passpoint network
1554      * @param bssid BSSID of the AP
1555      * @param isEss Block the ESS or the BSS
1556      * @param delay Delay in seconds
1557      */
blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay)1558     private void blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay) {
1559         PasspointProvider provider = mProviders.get(passpointUniqueId);
1560         if (provider != null) {
1561             provider.blockBssOrEss(bssid, isEss, delay);
1562         }
1563     }
1564 
1565     /**
1566      * Store the AnonymousIdentity for passpoint after connection.
1567      */
setAnonymousIdentity(WifiConfiguration configuration)1568     public void setAnonymousIdentity(WifiConfiguration configuration) {
1569         if (!configuration.isPasspoint()) {
1570             return;
1571         }
1572         PasspointProvider provider = mProviders.get(configuration.getProfileKey());
1573         if (provider != null) {
1574             provider.setAnonymousIdentity(configuration.enterpriseConfig.getAnonymousIdentity());
1575             mWifiConfigManager.saveToStore(true);
1576         }
1577     }
1578 
1579     /**
1580      * Resets all sim networks state.
1581      */
resetSimPasspointNetwork()1582     public void resetSimPasspointNetwork() {
1583         mProviders.values().stream().forEach(p -> p.setAnonymousIdentity(null));
1584         mWifiConfigManager.saveToStore(true);
1585     }
1586 
1587     /**
1588      * Handle Terms & Conditions acceptance required WNM-Notification event
1589      *
1590      * @param event Terms & Conditions WNM-Notification data
1591      * @param config Configuration of the currently connected Passpoint network
1592      *
1593      * @return The Terms & conditions URL if it is valid, null otherwise
1594      */
handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config)1595     public URL handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config) {
1596         if (event == null || config == null || !config.isPasspoint()) {
1597             return null;
1598         }
1599         final int oneHourInSeconds = 60 * 60;
1600         final int twentyFourHoursInSeconds = 24 * 60 * 60;
1601         final URL termsAndConditionsUrl;
1602         try {
1603             termsAndConditionsUrl = new URL(event.getUrl());
1604         } catch (java.net.MalformedURLException e) {
1605             Log.e(TAG, "Malformed Terms and Conditions URL: " + event.getUrl()
1606                     + " from BSSID: " + Utils.macToString(event.getBssid()));
1607 
1608             // Block this provider for an hour, this unlikely issue may be resolved shortly
1609             blockProvider(config.getProfileKey(), event.getBssid(), true, oneHourInSeconds);
1610             return null;
1611         }
1612         // Reject URLs that are not HTTPS
1613         if (!TextUtils.equals(termsAndConditionsUrl.getProtocol(), "https")) {
1614             Log.e(TAG, "Non-HTTPS Terms and Conditions URL rejected: " + termsAndConditionsUrl
1615                     + " from BSSID: " + Utils.macToString(event.getBssid()));
1616 
1617             // Block this provider for 24 hours, it is unlikely to be changed
1618             blockProvider(config.getProfileKey(), event.getBssid(), true,
1619                     twentyFourHoursInSeconds);
1620             return null;
1621         }
1622         Log.i(TAG, "Captive network, Terms and Conditions URL: " + termsAndConditionsUrl
1623                 + " from BSSID: " + Utils.macToString(event.getBssid()));
1624         return termsAndConditionsUrl;
1625     }
1626 
1627     /**
1628      * Check if Wi-Fi Passpoint is enabled.
1629      *
1630      * @return true if Wi-Fi Passpoint is enabled.
1631      */
isWifiPasspointEnabled()1632     public boolean isWifiPasspointEnabled() {
1633         return mEnabled;
1634     }
1635 
1636     /**
1637      * Enable or disable Wi-Fi Passpoint globally.
1638      */
setWifiPasspointEnabled(boolean enabled)1639     public void setWifiPasspointEnabled(boolean enabled) {
1640         if (enabled != mEnabled) {
1641             clearAnqpRequestsAndFlushCache();
1642             mEnabled = enabled;
1643             mSettingsStore.handleWifiPasspointEnabled(enabled);
1644         }
1645     }
1646 
1647     /**
1648      * Get the selected RCOI for a particular Passpoint network connection
1649      * @param uniqueId The Unique ID of the Passpoint configuration
1650      * @param ssid The target SSID
1651      * @return Selected RCOI for a network, or 0 if none.
1652      */
getSelectedRcoiForNetwork(String uniqueId, String ssid)1653     public long getSelectedRcoiForNetwork(String uniqueId, String ssid) {
1654         if (TextUtils.isEmpty(uniqueId) || TextUtils.isEmpty(ssid)) return 0;
1655         PasspointProvider provider = mProviders.get(uniqueId);
1656         if (provider == null) return 0;
1657         return provider.getAndRemoveMatchedRcoi(ssid);
1658     }
1659 }
1660