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