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