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