WifiConfigManager.java revision c301f93e031e6a2c470524f98045fad771545a7e
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
17package com.android.server.wifi;
18
19import android.app.ActivityManager;
20import android.app.admin.DeviceAdminInfo;
21import android.app.admin.DevicePolicyManagerInternal;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.net.IpConfiguration;
28import android.net.ProxyInfo;
29import android.net.StaticIpConfiguration;
30import android.net.wifi.ScanResult;
31import android.net.wifi.WifiConfiguration;
32import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
33import android.net.wifi.WifiEnterpriseConfig;
34import android.net.wifi.WifiInfo;
35import android.net.wifi.WifiManager;
36import android.net.wifi.WifiScanner;
37import android.os.RemoteException;
38import android.os.UserHandle;
39import android.os.UserManager;
40import android.provider.Settings;
41import android.telephony.TelephonyManager;
42import android.text.TextUtils;
43import android.util.LocalLog;
44import android.util.Log;
45
46import com.android.internal.R;
47import com.android.internal.annotations.VisibleForTesting;
48import com.android.server.LocalServices;
49import com.android.server.wifi.WifiConfigStoreLegacy.WifiConfigStoreDataLegacy;
50import com.android.server.wifi.util.ScanResultUtil;
51import com.android.server.wifi.util.TelephonyUtil;
52
53import org.xmlpull.v1.XmlPullParserException;
54
55import java.io.FileDescriptor;
56import java.io.IOException;
57import java.io.PrintWriter;
58import java.util.ArrayList;
59import java.util.BitSet;
60import java.util.Calendar;
61import java.util.Collection;
62import java.util.Collections;
63import java.util.HashMap;
64import java.util.HashSet;
65import java.util.Iterator;
66import java.util.List;
67import java.util.Map;
68import java.util.Set;
69
70/**
71 * This class provides the APIs to manage configured Wi-Fi networks.
72 * It deals with the following:
73 * - Maintaining a list of configured networks for quick access.
74 * - Persisting the configurations to store when required.
75 * - Supporting WifiManager Public API calls:
76 *   > addOrUpdateNetwork()
77 *   > removeNetwork()
78 *   > enableNetwork()
79 *   > disableNetwork()
80 * - Handle user switching on multi-user devices.
81 *
82 * All network configurations retrieved from this class are copies of the original configuration
83 * stored in the internal database. So, any updates to the retrieved configuration object are
84 * meaningless and will not be reflected in the original database.
85 * This is done on purpose to ensure that only WifiConfigManager can modify configurations stored
86 * in the internal database. Any configuration updates should be triggered with appropriate helper
87 * methods of this class using the configuration's unique networkId.
88 *
89 * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
90 */
91public class WifiConfigManager {
92    /**
93     * String used to mask passwords to public interface.
94     */
95    @VisibleForTesting
96    public static final String PASSWORD_MASK = "*";
97    /**
98     * Package name for SysUI. This is used to lookup the UID of SysUI which is used to allow
99     * Quick settings to modify network configurations.
100     */
101    @VisibleForTesting
102    public static final String SYSUI_PACKAGE_NAME = "com.android.systemui";
103    /**
104     * Network Selection disable reason thresholds. These numbers are used to debounce network
105     * failures before we disable them.
106     * These are indexed using the disable reason constants defined in
107     * {@link android.net.wifi.WifiConfiguration.NetworkSelectionStatus}.
108     */
109    @VisibleForTesting
110    public static final int[] NETWORK_SELECTION_DISABLE_THRESHOLD = {
111            -1, //  threshold for NETWORK_SELECTION_ENABLE
112            1,  //  threshold for DISABLED_BAD_LINK
113            5,  //  threshold for DISABLED_ASSOCIATION_REJECTION
114            5,  //  threshold for DISABLED_AUTHENTICATION_FAILURE
115            5,  //  threshold for DISABLED_DHCP_FAILURE
116            5,  //  threshold for DISABLED_DNS_FAILURE
117            1,  //  threshold for DISABLED_WPS_START
118            6,  //  threshold for DISABLED_TLS_VERSION_MISMATCH
119            1,  //  threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
120            1,  //  threshold for DISABLED_NO_INTERNET
121            1,  //  threshold for DISABLED_BY_WIFI_MANAGER
122            1   //  threshold for DISABLED_BY_USER_SWITCH
123    };
124    /**
125     * Network Selection disable timeout for each kind of error. After the timeout milliseconds,
126     * enable the network again.
127     * These are indexed using the disable reason constants defined in
128     * {@link android.net.wifi.WifiConfiguration.NetworkSelectionStatus}.
129     */
130    @VisibleForTesting
131    public static final int[] NETWORK_SELECTION_DISABLE_TIMEOUT_MS = {
132            Integer.MAX_VALUE,  // threshold for NETWORK_SELECTION_ENABLE
133            15 * 60 * 1000,     // threshold for DISABLED_BAD_LINK
134            5 * 60 * 1000,      // threshold for DISABLED_ASSOCIATION_REJECTION
135            5 * 60 * 1000,      // threshold for DISABLED_AUTHENTICATION_FAILURE
136            5 * 60 * 1000,      // threshold for DISABLED_DHCP_FAILURE
137            5 * 60 * 1000,      // threshold for DISABLED_DNS_FAILURE
138            0 * 60 * 1000,      // threshold for DISABLED_WPS_START
139            Integer.MAX_VALUE,  // threshold for DISABLED_TLS_VERSION
140            Integer.MAX_VALUE,  // threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
141            Integer.MAX_VALUE,  // threshold for DISABLED_NO_INTERNET
142            Integer.MAX_VALUE,  // threshold for DISABLED_BY_WIFI_MANAGER
143            Integer.MAX_VALUE   // threshold for DISABLED_BY_USER_SWITCH
144    };
145    /**
146     * Max size of scan details to cache in {@link #mScanDetailCaches}.
147     */
148    @VisibleForTesting
149    public static final int SCAN_CACHE_ENTRIES_MAX_SIZE = 192;
150    /**
151     * Once the size of the scan details in the cache {@link #mScanDetailCaches} exceeds
152     * {@link #SCAN_CACHE_ENTRIES_MAX_SIZE}, trim it down to this value so that we have some
153     * buffer time before the next eviction.
154     */
155    @VisibleForTesting
156    public static final int SCAN_CACHE_ENTRIES_TRIM_SIZE = 128;
157    /**
158     * Link networks only if they have less than this number of scan cache entries.
159     */
160    @VisibleForTesting
161    public static final int LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES = 6;
162    /**
163     * Link networks only if the bssid in scan results for the networks match in the first
164     * 16 ASCII chars in the bssid string. For example = "af:de:56;34:15:7"
165     */
166    @VisibleForTesting
167    public static final int LINK_CONFIGURATION_BSSID_MATCH_LENGTH = 16;
168    /**
169     * Flags to be passed in for |canModifyNetwork| to decide if the change is minor and can
170     * bypass the lockdown checks.
171     */
172    private static final boolean ALLOW_LOCKDOWN_CHECK_BYPASS = true;
173    private static final boolean DISALLOW_LOCKDOWN_CHECK_BYPASS = false;
174    /**
175     * Log tag for this class.
176     */
177    private static final String TAG = "WifiConfigManager";
178    /**
179     * Maximum age of scan results that can be used for averaging out RSSI value.
180     */
181    private static final int SCAN_RESULT_MAXIMUM_AGE_MS = 40000;
182    /**
183     * General sorting algorithm of all networks for scanning purposes:
184     * Place the configurations in descending order of their |numAssociation| values. If networks
185     * have the same |numAssociation|, place the configurations with
186     * |lastSeenInQualifiedNetworkSelection| set first.
187     */
188    private static final WifiConfigurationUtil.WifiConfigurationComparator sScanListComparator =
189            new WifiConfigurationUtil.WifiConfigurationComparator() {
190                @Override
191                public int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b) {
192                    if (a.numAssociation != b.numAssociation) {
193                        return Long.compare(b.numAssociation, a.numAssociation);
194                    } else {
195                        boolean isConfigALastSeen =
196                                a.getNetworkSelectionStatus()
197                                        .getSeenInLastQualifiedNetworkSelection();
198                        boolean isConfigBLastSeen =
199                                b.getNetworkSelectionStatus()
200                                        .getSeenInLastQualifiedNetworkSelection();
201                        return Boolean.compare(isConfigBLastSeen, isConfigALastSeen);
202                    }
203                }
204            };
205
206    /**
207     * List of external dependencies for WifiConfigManager.
208     */
209    private final Context mContext;
210    private final FrameworkFacade mFacade;
211    private final Clock mClock;
212    private final UserManager mUserManager;
213    private final BackupManagerProxy mBackupManagerProxy;
214    private final TelephonyManager mTelephonyManager;
215    private final WifiKeyStore mWifiKeyStore;
216    private final WifiConfigStore mWifiConfigStore;
217    private final WifiConfigStoreLegacy mWifiConfigStoreLegacy;
218    /**
219     * Local log used for debugging any WifiConfigManager issues.
220     */
221    private final LocalLog mLocalLog =
222            new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 128 : 256);
223    /**
224     * Map of configured networks with network id as the key.
225     */
226    private final ConfigurationMap mConfiguredNetworks;
227    /**
228     * Stores a map of NetworkId to ScanDetailCache.
229     */
230    private final Map<Integer, ScanDetailCache> mScanDetailCaches;
231    /**
232     * Framework keeps a list of ephemeral SSIDs that where deleted by user,
233     * so as, framework knows not to autoconnect again those SSIDs based on scorer input.
234     * The list is never cleared up.
235     * The SSIDs are encoded in a String as per definition of WifiConfiguration.SSID field.
236     */
237    private final Set<String> mDeletedEphemeralSSIDs;
238    /**
239     * Flag to indicate if only networks with the same psk should be linked.
240     * TODO(b/30706406): Remove this flag if unused.
241     */
242    private final boolean mOnlyLinkSameCredentialConfigurations;
243    /**
244     * Number of channels to scan for during partial scans initiated while connected.
245     */
246    private final int mMaxNumActiveChannelsForPartialScans;
247    /**
248     * Verbose logging flag. Toggled by developer options.
249     */
250    private boolean mVerboseLoggingEnabled = false;
251    /**
252     * Current logged in user ID.
253     */
254    private int mCurrentUserId = UserHandle.USER_SYSTEM;
255    /**
256     * This is keeping track of the next network ID to be assigned. Any new networks will be
257     * assigned |mNextNetworkId| as network ID.
258     */
259    private int mNextNetworkId = 0;
260    /**
261     * UID of system UI. This uid is allowed to modify network configurations regardless of which
262     * user is logged in.
263     */
264    private int mSystemUiUid = -1;
265    /**
266     * This is used to remember which network was selected successfully last by an app. This is set
267     * when an app invokes {@link #enableNetwork(int, boolean, int)} with |disableOthers| flag set.
268     * This is the only way for an app to request connection to a specific network using the
269     * {@link WifiManager} API's.
270     */
271    private int mLastSelectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
272    private long mLastSelectedTimeStamp =
273            WifiConfiguration.NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
274
275    /**
276     * Create new instance of WifiConfigManager.
277     */
278    WifiConfigManager(
279            Context context, FrameworkFacade facade, Clock clock, UserManager userManager,
280            TelephonyManager telephonyManager, WifiKeyStore wifiKeyStore,
281            WifiConfigStore wifiConfigStore, WifiConfigStoreLegacy wifiConfigStoreLegacy) {
282        mContext = context;
283        mFacade = facade;
284        mClock = clock;
285        mUserManager = userManager;
286        mBackupManagerProxy = new BackupManagerProxy();
287        mTelephonyManager = telephonyManager;
288        mWifiKeyStore = wifiKeyStore;
289        mWifiConfigStore = wifiConfigStore;
290        mWifiConfigStoreLegacy = wifiConfigStoreLegacy;
291
292        mConfiguredNetworks = new ConfigurationMap(userManager);
293        mScanDetailCaches = new HashMap<>(16, 0.75f);
294        mDeletedEphemeralSSIDs = new HashSet<>();
295
296        mOnlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
297                R.bool.config_wifi_only_link_same_credential_configurations);
298        mMaxNumActiveChannelsForPartialScans = mContext.getResources().getInteger(
299                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels);
300
301        try {
302            mSystemUiUid = mContext.getPackageManager().getPackageUidAsUser(SYSUI_PACKAGE_NAME,
303                    PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
304        } catch (PackageManager.NameNotFoundException e) {
305            Log.e(TAG, "Unable to resolve SystemUI's UID.");
306        }
307    }
308
309    /**
310     * Construct the string to be put in the |creationTime| & |updateTime| elements of
311     * WifiConfiguration from the provided wall clock millis.
312     *
313     * @param wallClockMillis Time in milliseconds to be converted to string.
314     */
315    @VisibleForTesting
316    public static String createDebugTimeStampString(long wallClockMillis) {
317        StringBuilder sb = new StringBuilder();
318        sb.append("time=");
319        Calendar c = Calendar.getInstance();
320        c.setTimeInMillis(wallClockMillis);
321        sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
322        return sb.toString();
323    }
324
325    /**
326     * Enable/disable verbose logging in WifiConfigManager & its helper classes.
327     */
328    public void enableVerboseLogging(int verbose) {
329        if (verbose > 0) {
330            mVerboseLoggingEnabled = true;
331        } else {
332            mVerboseLoggingEnabled = false;
333        }
334        mWifiConfigStore.enableVerboseLogging(mVerboseLoggingEnabled);
335        mWifiKeyStore.enableVerboseLogging(mVerboseLoggingEnabled);
336    }
337
338    /**
339     * Helper method to mask all passwords/keys from the provided WifiConfiguration object. This
340     * is needed when the network configurations are being requested via the public WifiManager
341     * API's.
342     * This currently masks the following elements: psk, wepKeys & enterprise config password.
343     */
344    private void maskPasswordsInWifiConfiguration(WifiConfiguration configuration) {
345        if (!TextUtils.isEmpty(configuration.preSharedKey)) {
346            configuration.preSharedKey = PASSWORD_MASK;
347        }
348        if (configuration.wepKeys != null) {
349            for (int i = 0; i < configuration.wepKeys.length; i++) {
350                if (!TextUtils.isEmpty(configuration.wepKeys[i])) {
351                    configuration.wepKeys[i] = PASSWORD_MASK;
352                }
353            }
354        }
355        if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
356            configuration.enterpriseConfig.setPassword(PASSWORD_MASK);
357        }
358    }
359
360    /**
361     * Helper method to create a copy of the provided internal WifiConfiguration object to be
362     * passed to external modules.
363     *
364     * @param configuration provided WifiConfiguration object.
365     * @param maskPasswords Mask passwords or not.
366     * @return Copy of the WifiConfiguration object.
367     */
368    private WifiConfiguration createExternalWifiConfiguration(
369            WifiConfiguration configuration, boolean maskPasswords) {
370        WifiConfiguration network = new WifiConfiguration(configuration);
371        if (maskPasswords) {
372            maskPasswordsInWifiConfiguration(network);
373        }
374        return network;
375    }
376
377    /**
378     * Fetch the list of currently configured networks maintained in WifiConfigManager.
379     *
380     * This retrieves a copy of the internal configurations maintained by WifiConfigManager and
381     * should be used for any public interfaces.
382     *
383     * @param savedOnly     Retrieve only saved networks.
384     * @param maskPasswords Mask passwords or not.
385     * @return List of WifiConfiguration objects representing the networks.
386     */
387    private List<WifiConfiguration> getConfiguredNetworks(
388            boolean savedOnly, boolean maskPasswords) {
389        List<WifiConfiguration> networks = new ArrayList<>();
390        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
391            if (savedOnly && config.ephemeral) {
392                continue;
393            }
394            networks.add(createExternalWifiConfiguration(config, maskPasswords));
395        }
396        return networks;
397    }
398
399    /**
400     * Retrieves the list of all configured networks with passwords masked.
401     *
402     * @return List of WifiConfiguration objects representing the networks.
403     */
404    public List<WifiConfiguration> getConfiguredNetworks() {
405        return getConfiguredNetworks(false, true);
406    }
407
408    /**
409     * Retrieves the list of all configured networks with the passwords in plaintext.
410     *
411     * WARNING: Don't use this to pass network configurations to external apps. Should only be
412     * sent to system apps/wifi stack, when there is a need for passwords in plaintext.
413     * TODO: Need to understand the current use case of this API.
414     *
415     * @return List of WifiConfiguration objects representing the networks.
416     */
417    public List<WifiConfiguration> getConfiguredNetworksWithPasswords() {
418        return getConfiguredNetworks(false, false);
419    }
420
421    /**
422     * Retrieves the list of all configured networks with the passwords masked.
423     *
424     * @return List of WifiConfiguration objects representing the networks.
425     */
426    public List<WifiConfiguration> getSavedNetworks() {
427        return getConfiguredNetworks(true, true);
428    }
429
430    /**
431     * Retrieves the configured network corresponding to the provided networkId with password
432     * masked.
433     *
434     * @param networkId networkId of the requested network.
435     * @return WifiConfiguration object if found, null otherwise.
436     */
437    public WifiConfiguration getConfiguredNetwork(int networkId) {
438        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
439        if (config == null) {
440            return null;
441        }
442        // Create a new configuration object with the passwords masked to send out to the external
443        // world.
444        return createExternalWifiConfiguration(config, true);
445    }
446
447    /**
448     * Retrieves the configured network corresponding to the provided config key with password
449     * masked.
450     *
451     * @param configKey configKey of the requested network.
452     * @return WifiConfiguration object if found, null otherwise.
453     */
454    public WifiConfiguration getConfiguredNetwork(String configKey) {
455        WifiConfiguration config = getInternalConfiguredNetwork(configKey);
456        if (config == null) {
457            return null;
458        }
459        // Create a new configuration object with the passwords masked to send out to the external
460        // world.
461        return createExternalWifiConfiguration(config, true);
462    }
463
464    /**
465     * Retrieves the configured network corresponding to the provided networkId with password
466     * in plaintext.
467     *
468     * WARNING: Don't use this to pass network configurations to external apps. Should only be
469     * sent to system apps/wifi stack, when there is a need for passwords in plaintext.
470     *
471     * @param networkId networkId of the requested network.
472     * @return WifiConfiguration object if found, null otherwise.
473     */
474    public WifiConfiguration getConfiguredNetworkWithPassword(int networkId) {
475        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
476        if (config == null) {
477            return null;
478        }
479        // Create a new configuration object without the passwords masked to send out to the
480        // external world.
481        return createExternalWifiConfiguration(config, false);
482    }
483
484    /**
485     * Helper method to retrieve all the internal WifiConfiguration objects corresponding to all
486     * the networks in our database.
487     */
488    private Collection<WifiConfiguration> getInternalConfiguredNetworks() {
489        return mConfiguredNetworks.valuesForCurrentUser();
490    }
491
492    /**
493     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
494     * provided configuration in our database.
495     * This first attempts to find the network using the provided network ID in configuration,
496     * else it attempts to find a matching configuration using the configKey.
497     */
498    private WifiConfiguration getInternalConfiguredNetwork(WifiConfiguration config) {
499        WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(config.networkId);
500        if (internalConfig != null) {
501            return internalConfig;
502        }
503        internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
504        if (internalConfig == null) {
505            Log.e(TAG, "Cannot find network with networkId " + config.networkId
506                    + " or configKey " + config.configKey());
507        }
508        return internalConfig;
509    }
510
511    /**
512     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
513     * provided network ID in our database.
514     */
515    private WifiConfiguration getInternalConfiguredNetwork(int networkId) {
516        WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(networkId);
517        if (internalConfig == null) {
518            Log.e(TAG, "Cannot find network with networkId " + networkId);
519        }
520        return internalConfig;
521    }
522
523    /**
524     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
525     * provided configKey in our database.
526     */
527    private WifiConfiguration getInternalConfiguredNetwork(String configKey) {
528        WifiConfiguration internalConfig =
529                mConfiguredNetworks.getByConfigKeyForCurrentUser(configKey);
530        if (internalConfig == null) {
531            Log.e(TAG, "Cannot find network with configKey " + configKey);
532        }
533        return internalConfig;
534    }
535
536    /**
537     * Method to send out the configured networks change broadcast when a single network
538     * configuration is changed.
539     *
540     * @param network WifiConfiguration corresponding to the network that was changed.
541     * @param reason  The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED,
542     *                WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE.
543     */
544    private void sendConfiguredNetworkChangedBroadcast(
545            WifiConfiguration network, int reason) {
546        Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
547        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
548        intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
549        // Create a new WifiConfiguration with passwords masked before we send it out.
550        WifiConfiguration broadcastNetwork = new WifiConfiguration(network);
551        maskPasswordsInWifiConfiguration(broadcastNetwork);
552        intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, broadcastNetwork);
553        intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
554        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
555    }
556
557    /**
558     * Method to send out the configured networks change broadcast when multiple network
559     * configurations are changed.
560     */
561    private void sendConfiguredNetworksChangedBroadcast() {
562        Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
563        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
564        intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
565        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
566    }
567
568    /**
569     * Checks if the app has the permission to override Wi-Fi network configuration or not.
570     *
571     * @param uid uid of the app.
572     * @return true if the app does have the permission, false otherwise.
573     */
574    public boolean checkConfigOverridePermission(int uid) {
575        try {
576            int permission =
577                    mFacade.checkUidPermission(
578                            android.Manifest.permission.OVERRIDE_WIFI_CONFIG, uid);
579            return (permission == PackageManager.PERMISSION_GRANTED);
580        } catch (RemoteException e) {
581            Log.e(TAG, "Error checking for permission " + e);
582            return false;
583        }
584    }
585
586    /**
587     * Checks if |uid| has permission to modify the provided configuration.
588     *
589     * @param config         WifiConfiguration object corresponding to the network to be modified.
590     * @param uid            UID of the app requesting the modification.
591     * @param ignoreLockdown Ignore the configuration lockdown checks for connection attempts.
592     */
593    private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
594        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
595                DevicePolicyManagerInternal.class);
596
597        final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
598                DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
599
600        // If |uid| corresponds to the device owner, allow all modifications.
601        if (isUidDeviceOwner) {
602            return true;
603        }
604
605        final boolean isCreator = (config.creatorUid == uid);
606
607        // Check if the |uid| holds the |OVERRIDE_CONFIG_WIFI| permission if the caller asks us to
608        // bypass the lockdown checks.
609        if (ignoreLockdown) {
610            return checkConfigOverridePermission(uid);
611        }
612
613        // Check if device has DPM capability. If it has and |dpmi| is still null, then we
614        // treat this case with suspicion and bail out.
615        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
616                && dpmi == null) {
617            Log.w(TAG, "Error retrieving DPMI service.");
618            return false;
619        }
620
621        // WiFi config lockdown related logic. At this point we know uid is NOT a Device Owner.
622        final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
623                config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
624        if (!isConfigEligibleForLockdown) {
625            return isCreator || checkConfigOverridePermission(uid);
626        }
627
628        final ContentResolver resolver = mContext.getContentResolver();
629        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
630                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
631        return !isLockdownFeatureEnabled && checkConfigOverridePermission(uid);
632    }
633
634    /**
635     * Method to check if the provided UID belongs to the current foreground user or some other
636     * app (only SysUI today) running on behalf of the user.
637     * This is used to prevent any background user apps from modifying network configurations.
638     *
639     * @param uid uid of the app.
640     * @return true if the UID belongs to the current foreground app or SystemUI, false otherwise.
641     */
642    private boolean doesUidBelongToCurrentUser(int uid) {
643        return (WifiConfigurationUtil.doesUidBelongToAnyProfile(
644                uid, mUserManager.getProfiles(mCurrentUserId)) || (uid == mSystemUiUid));
645    }
646
647    /**
648     * Copy over public elements from an external WifiConfiguration object to the internal
649     * configuration object if element has been set in the provided external WifiConfiguration.
650     * The only exception is the hidden |IpConfiguration| parameters, these need to be copied over
651     * for every update.
652     *
653     * This method updates all elements that are common to both network addition & update.
654     * The following fields of {@link WifiConfiguration} are not copied from external configs:
655     *  > networkId - These are allocated by Wi-Fi stack internally for any new configurations.
656     *  > status - The status needs to be explicitly updated using
657     *             {@link WifiManager#enableNetwork(int, boolean)} or
658     *             {@link WifiManager#disableNetwork(int)}.
659     *
660     * @param internalConfig WifiConfiguration object in our internal map.
661     * @param externalConfig WifiConfiguration object provided from the external API.
662     */
663    private void mergeWithInternalWifiConfiguration(
664            WifiConfiguration internalConfig, WifiConfiguration externalConfig) {
665        if (externalConfig.SSID != null) {
666            internalConfig.SSID = externalConfig.SSID;
667        }
668        if (externalConfig.BSSID != null) {
669            internalConfig.BSSID = externalConfig.BSSID;
670        }
671        internalConfig.hiddenSSID = externalConfig.hiddenSSID;
672        if (externalConfig.preSharedKey != null
673                && !externalConfig.preSharedKey.equals(PASSWORD_MASK)) {
674            internalConfig.preSharedKey = externalConfig.preSharedKey;
675        }
676        // Modify only wep keys are present in the provided configuration. This is a little tricky
677        // because there is no easy way to tell if the app is actually trying to null out the
678        // existing keys or not.
679        if (externalConfig.wepKeys != null) {
680            boolean hasWepKey = false;
681            for (int i = 0; i < internalConfig.wepKeys.length; i++) {
682                if (externalConfig.wepKeys[i] != null
683                        && !externalConfig.wepKeys[i].equals(PASSWORD_MASK)) {
684                    internalConfig.wepKeys[i] = externalConfig.wepKeys[i];
685                    hasWepKey = true;
686                }
687            }
688            if (hasWepKey) {
689                internalConfig.wepTxKeyIndex = externalConfig.wepTxKeyIndex;
690            }
691        }
692        if (externalConfig.FQDN != null) {
693            internalConfig.FQDN = externalConfig.FQDN;
694        }
695        if (externalConfig.providerFriendlyName != null) {
696            internalConfig.providerFriendlyName = externalConfig.providerFriendlyName;
697        }
698        if (externalConfig.roamingConsortiumIds != null) {
699            internalConfig.roamingConsortiumIds = externalConfig.roamingConsortiumIds.clone();
700        }
701
702        // Copy over all the auth/protocol/key mgmt parameters if set.
703        if (externalConfig.allowedAuthAlgorithms != null
704                && !externalConfig.allowedAuthAlgorithms.isEmpty()) {
705            internalConfig.allowedAuthAlgorithms =
706                    (BitSet) externalConfig.allowedAuthAlgorithms.clone();
707        }
708        if (externalConfig.allowedProtocols != null
709                && !externalConfig.allowedProtocols.isEmpty()) {
710            internalConfig.allowedProtocols = (BitSet) externalConfig.allowedProtocols.clone();
711        }
712        if (externalConfig.allowedKeyManagement != null
713                && !externalConfig.allowedKeyManagement.isEmpty()) {
714            internalConfig.allowedKeyManagement =
715                    (BitSet) externalConfig.allowedKeyManagement.clone();
716        }
717        if (externalConfig.allowedPairwiseCiphers != null
718                && !externalConfig.allowedPairwiseCiphers.isEmpty()) {
719            internalConfig.allowedPairwiseCiphers =
720                    (BitSet) externalConfig.allowedPairwiseCiphers.clone();
721        }
722        if (externalConfig.allowedGroupCiphers != null
723                && !externalConfig.allowedGroupCiphers.isEmpty()) {
724            internalConfig.allowedGroupCiphers =
725                    (BitSet) externalConfig.allowedGroupCiphers.clone();
726        }
727
728        // Copy over the |IpConfiguration| parameters if set.
729        if (externalConfig.getIpConfiguration() != null) {
730            IpConfiguration.IpAssignment ipAssignment = externalConfig.getIpAssignment();
731            if (ipAssignment != IpConfiguration.IpAssignment.UNASSIGNED) {
732                internalConfig.setIpAssignment(ipAssignment);
733                if (ipAssignment == IpConfiguration.IpAssignment.STATIC) {
734                    internalConfig.setStaticIpConfiguration(
735                            new StaticIpConfiguration(externalConfig.getStaticIpConfiguration()));
736                }
737            }
738            IpConfiguration.ProxySettings proxySettings = externalConfig.getProxySettings();
739            if (proxySettings != IpConfiguration.ProxySettings.UNASSIGNED) {
740                internalConfig.setProxySettings(proxySettings);
741                if (proxySettings == IpConfiguration.ProxySettings.PAC
742                        || proxySettings == IpConfiguration.ProxySettings.STATIC) {
743                    internalConfig.setHttpProxy(new ProxyInfo(externalConfig.getHttpProxy()));
744                }
745            }
746        }
747
748        // Copy over the |WifiEnterpriseConfig| parameters if set.
749        if (externalConfig.enterpriseConfig != null) {
750            internalConfig.enterpriseConfig =
751                    new WifiEnterpriseConfig(externalConfig.enterpriseConfig);
752        }
753    }
754
755    /**
756     * Set all the exposed defaults in the newly created WifiConfiguration object.
757     * These fields have a default value advertised in our public documentation. The only exception
758     * is the hidden |IpConfiguration| parameters, these have a default value even though they're
759     * hidden.
760     *
761     * @param configuration provided WifiConfiguration object.
762     */
763    private void setDefaultsInWifiConfiguration(WifiConfiguration configuration) {
764        configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
765
766        configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
767        configuration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
768
769        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
770        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
771
772        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
773        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
774
775        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
776        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
777        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
778        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
779
780        configuration.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
781        configuration.setProxySettings(IpConfiguration.ProxySettings.NONE);
782
783        configuration.status = WifiConfiguration.Status.DISABLED;
784        configuration.getNetworkSelectionStatus().setNetworkSelectionStatus(
785                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
786    }
787
788    /**
789     * Create a new internal WifiConfiguration object by copying over parameters from the provided
790     * external configuration and set defaults for the appropriate parameters.
791     *
792     * @param externalConfig WifiConfiguration object provided from the external API.
793     * @return New WifiConfiguration object with parameters merged from the provided external
794     * configuration.
795     */
796    private WifiConfiguration createNewInternalWifiConfigurationFromExternal(
797            WifiConfiguration externalConfig, int uid) {
798        WifiConfiguration newInternalConfig = new WifiConfiguration();
799
800        // First allocate a new network ID for the configuration.
801        newInternalConfig.networkId = mNextNetworkId++;
802
803        // First set defaults in the new configuration created.
804        setDefaultsInWifiConfiguration(newInternalConfig);
805
806        // Copy over all the public elements from the provided configuration.
807        mergeWithInternalWifiConfiguration(newInternalConfig, externalConfig);
808
809        // Copy over the hidden configuration parameters. These are the only parameters used by
810        // system apps to indicate some property about the network being added.
811        // These are only copied over for network additions and ignored for network updates.
812        newInternalConfig.requirePMF = externalConfig.requirePMF;
813        newInternalConfig.noInternetAccessExpected = externalConfig.noInternetAccessExpected;
814        newInternalConfig.ephemeral = externalConfig.ephemeral;
815        newInternalConfig.meteredHint = externalConfig.meteredHint;
816        newInternalConfig.useExternalScores = externalConfig.useExternalScores;
817        newInternalConfig.shared = externalConfig.shared;
818
819        // Add debug information for network addition.
820        newInternalConfig.creatorUid = newInternalConfig.lastUpdateUid = uid;
821        newInternalConfig.creatorName = newInternalConfig.lastUpdateName =
822                mContext.getPackageManager().getNameForUid(uid);
823        newInternalConfig.creationTime = newInternalConfig.updateTime =
824                createDebugTimeStampString(mClock.getWallClockMillis());
825
826        return newInternalConfig;
827    }
828
829    /**
830     * Create a new internal WifiConfiguration object by copying over parameters from the provided
831     * external configuration to a copy of the existing internal WifiConfiguration object.
832     *
833     * @param internalConfig WifiConfiguration object in our internal map.
834     * @param externalConfig WifiConfiguration object provided from the external API.
835     * @return Copy of existing WifiConfiguration object with parameters merged from the provided
836     * configuration.
837     */
838    private WifiConfiguration updateExistingInternalWifiConfigurationFromExternal(
839            WifiConfiguration internalConfig, WifiConfiguration externalConfig, int uid) {
840        WifiConfiguration newInternalConfig = new WifiConfiguration(internalConfig);
841
842        // Copy over all the public elements from the provided configuration.
843        mergeWithInternalWifiConfiguration(newInternalConfig, externalConfig);
844
845        // Add debug information for network update.
846        newInternalConfig.lastUpdateUid = uid;
847        newInternalConfig.lastUpdateName = mContext.getPackageManager().getNameForUid(uid);
848        newInternalConfig.updateTime = createDebugTimeStampString(mClock.getWallClockMillis());
849
850        return newInternalConfig;
851    }
852
853    /**
854     * Add a network or update a network configuration to our database.
855     * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
856     * network configuration. Otherwise, the networkId should refer to an existing configuration.
857     *
858     * @param config provided WifiConfiguration object.
859     * @param uid    UID of the app requesting the network addition/deletion.
860     * @return NetworkUpdateResult object representing status of the update.
861     */
862    private NetworkUpdateResult addOrUpdateNetworkInternal(WifiConfiguration config, int uid) {
863        if (mVerboseLoggingEnabled) {
864            Log.v(TAG, "Adding/Updating network " + config.getPrintableSsid());
865        }
866        WifiConfiguration newInternalConfig = null;
867
868        // First check if we already have a network with the provided network id or configKey.
869        WifiConfiguration existingInternalConfig = getInternalConfiguredNetwork(config);
870        // No existing network found. So, potentially a network add.
871        if (existingInternalConfig == null) {
872            newInternalConfig = createNewInternalWifiConfigurationFromExternal(config, uid);
873            // Since the original config provided may have had an empty
874            // {@link WifiConfiguration#allowedKeyMgmt} field, check again if we already have a
875            // network with the the same configkey.
876            existingInternalConfig = getInternalConfiguredNetwork(newInternalConfig.configKey());
877        }
878        // Existing network found. So, a network update.
879        if (existingInternalConfig != null) {
880            // Check for the app's permission before we let it update this network.
881            if (!canModifyNetwork(existingInternalConfig, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
882                Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
883                        + config.configKey());
884                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
885            }
886            newInternalConfig =
887                    updateExistingInternalWifiConfigurationFromExternal(
888                            existingInternalConfig, config, uid);
889        }
890
891        // Update the keys for enterprise networks.
892        if (config.enterpriseConfig != null
893                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
894            if (!(mWifiKeyStore.updateNetworkKeys(newInternalConfig, existingInternalConfig))) {
895                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
896            }
897        }
898
899        boolean newNetwork = (existingInternalConfig == null);
900        // Reset the |hasEverConnected| flag if the credential parameters changed in this update.
901        boolean hasCredentialChanged =
902                newNetwork || WifiConfigurationUtil.hasCredentialChanged(
903                        existingInternalConfig, newInternalConfig);
904        if (hasCredentialChanged) {
905            newInternalConfig.getNetworkSelectionStatus().setHasEverConnected(false);
906        }
907
908        // Add it to our internal map. This will replace any existing network configuration for
909        // updates.
910        mConfiguredNetworks.put(newInternalConfig);
911
912        if (mDeletedEphemeralSSIDs.remove(config.SSID)) {
913            if (mVerboseLoggingEnabled) {
914                Log.v(TAG, "Removed from ephemeral blacklist: " + config.SSID);
915            }
916        }
917
918        // Stage the backup of the SettingsProvider package which backs this up.
919        mBackupManagerProxy.notifyDataChanged();
920
921        // This is needed to inform IpManager about any IP configuration changes.
922        boolean hasIpChanged =
923                newNetwork || WifiConfigurationUtil.hasIpChanged(
924                        existingInternalConfig, newInternalConfig);
925        boolean hasProxyChanged =
926                newNetwork || WifiConfigurationUtil.hasProxyChanged(
927                        existingInternalConfig, newInternalConfig);
928        NetworkUpdateResult result = new NetworkUpdateResult(hasIpChanged, hasProxyChanged);
929        result.setIsNewNetwork(newNetwork);
930        result.setNetworkId(newInternalConfig.networkId);
931
932        localLog("addOrUpdateNetworkInternal: added/updated config."
933                + " netId=" + newInternalConfig.networkId
934                + " configKey=" + newInternalConfig.configKey()
935                + " uid=" + Integer.toString(newInternalConfig.creatorUid)
936                + " name=" + newInternalConfig.creatorName);
937        return result;
938    }
939
940    /**
941     * Add a network or update a network configuration to our database.
942     * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
943     * network configuration. Otherwise, the networkId should refer to an existing configuration.
944     *
945     * @param config provided WifiConfiguration object.
946     * @param uid    UID of the app requesting the network addition/modification.
947     * @return NetworkUpdateResult object representing status of the update.
948     */
949    public NetworkUpdateResult addOrUpdateNetwork(WifiConfiguration config, int uid) {
950        if (!doesUidBelongToCurrentUser(uid)) {
951            Log.e(TAG, "UID " + uid + " not visible to the current user");
952            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
953        }
954        if (config == null) {
955            Log.e(TAG, "Cannot add/update network with null config");
956            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
957        }
958        NetworkUpdateResult result = addOrUpdateNetworkInternal(config, uid);
959        if (!result.isSuccess()) {
960            Log.e(TAG, "Failed to add/update network " + config.getPrintableSsid());
961            return result;
962        }
963        WifiConfiguration newConfig = getInternalConfiguredNetwork(result.getNetworkId());
964        sendConfiguredNetworkChangedBroadcast(
965                newConfig,
966                result.isNewNetwork()
967                        ? WifiManager.CHANGE_REASON_ADDED
968                        : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
969        // External modification, persist it immediately.
970        saveToStore(true);
971        return result;
972    }
973
974    /**
975     * Removes the specified network configuration from our database.
976     *
977     * @param config provided WifiConfiguration object.
978     * @return true if successful, false otherwise.
979     */
980    private boolean removeNetworkInternal(WifiConfiguration config) {
981        if (mVerboseLoggingEnabled) {
982            Log.v(TAG, "Removing network " + config.getPrintableSsid());
983        }
984        // Remove any associated enterprise keys.
985        if (config.enterpriseConfig != null
986                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
987            mWifiKeyStore.removeKeys(config.enterpriseConfig);
988        }
989
990        removeConnectChoiceFromAllNetworks(config.configKey());
991        mConfiguredNetworks.remove(config.networkId);
992        mScanDetailCaches.remove(config.networkId);
993        // Stage the backup of the SettingsProvider package which backs this up.
994        mBackupManagerProxy.notifyDataChanged();
995
996        localLog("removeNetworkInternal: removed config."
997                + " netId=" + config.networkId
998                + " configKey=" + config.configKey());
999        return true;
1000    }
1001
1002    /**
1003     * Removes the specified network configuration from our database.
1004     *
1005     * @param networkId network ID of the provided network.
1006     * @param uid       UID of the app requesting the network deletion.
1007     * @return true if successful, false otherwise.
1008     */
1009    public boolean removeNetwork(int networkId, int uid) {
1010        if (!doesUidBelongToCurrentUser(uid)) {
1011            Log.e(TAG, "UID " + uid + " not visible to the current user");
1012            return false;
1013        }
1014        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1015        if (config == null) {
1016            return false;
1017        }
1018        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
1019            Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
1020                    + config.configKey());
1021            return false;
1022        }
1023        if (!removeNetworkInternal(config)) {
1024            Log.e(TAG, "Failed to remove network " + config.getPrintableSsid());
1025            return false;
1026        }
1027        if (networkId == mLastSelectedNetworkId) {
1028            clearLastSelectedNetwork();
1029        }
1030        sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
1031        // External modification, persist it immediately.
1032        saveToStore(true);
1033        return true;
1034    }
1035
1036    /**
1037     * Remove all networks associated with an application.
1038     *
1039     * @param app Application info of the package of networks to remove.
1040     * @return true if all networks removed successfully, false otherwise
1041     */
1042    public boolean removeNetworksForApp(ApplicationInfo app) {
1043        if (app == null || app.packageName == null) {
1044            return false;
1045        }
1046        Log.d(TAG, "Remove all networks for app " + app);
1047        boolean success = true;
1048        WifiConfiguration[] copiedConfigs =
1049                mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
1050        for (WifiConfiguration config : copiedConfigs) {
1051            if (app.uid != config.creatorUid || !app.packageName.equals(config.creatorName)) {
1052                continue;
1053            }
1054            localLog("Removing network " + config.SSID
1055                    + ", application \"" + app.packageName + "\" uninstalled"
1056                    + " from user " + UserHandle.getUserId(app.uid));
1057            success &= removeNetwork(config.networkId, app.uid);
1058        }
1059        return success;
1060    }
1061
1062    /**
1063     * Remove all networks associated with a user.
1064     *
1065     * @param userId The identifier of the user which is being removed.
1066     * @return true if all networks removed successfully, false otherwise
1067     */
1068    boolean removeNetworksForUser(int userId) {
1069        Log.d(TAG, "Remove all networks for user " + userId);
1070        boolean success = true;
1071        WifiConfiguration[] copiedConfigs =
1072                mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
1073        for (WifiConfiguration config : copiedConfigs) {
1074            if (userId != UserHandle.getUserId(config.creatorUid)) {
1075                continue;
1076            }
1077            success &= removeNetwork(config.networkId, config.creatorUid);
1078            localLog("Removing network " + config.SSID + ", user " + userId + " removed");
1079        }
1080        return success;
1081    }
1082
1083    /**
1084     * Helper method to mark a network enabled for network selection.
1085     */
1086    private void setNetworkSelectionEnabled(NetworkSelectionStatus status) {
1087        status.setNetworkSelectionStatus(
1088                NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
1089        status.setDisableTime(
1090                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1091        status.setNetworkSelectionDisableReason(NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
1092
1093        // Clear out all the disable reason counters.
1094        status.clearDisableReasonCounter();
1095    }
1096
1097    /**
1098     * Helper method to mark a network temporarily disabled for network selection.
1099     */
1100    private void setNetworkSelectionTemporarilyDisabled(
1101            NetworkSelectionStatus status, int disableReason) {
1102        status.setNetworkSelectionStatus(
1103                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
1104        // Only need a valid time filled in for temporarily disabled networks.
1105        status.setDisableTime(mClock.getElapsedSinceBootMillis());
1106        status.setNetworkSelectionDisableReason(disableReason);
1107    }
1108
1109    /**
1110     * Helper method to mark a network permanently disabled for network selection.
1111     */
1112    private void setNetworkSelectionPermanentlyDisabled(
1113            NetworkSelectionStatus status, int disableReason) {
1114        status.setNetworkSelectionStatus(
1115                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
1116        status.setDisableTime(
1117                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1118        status.setNetworkSelectionDisableReason(disableReason);
1119    }
1120
1121    /**
1122     * Helper method to set the publicly exposed status for the network and send out the network
1123     * status change broadcast.
1124     */
1125    private void setNetworkStatus(WifiConfiguration config, int status) {
1126        config.status = status;
1127        sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
1128    }
1129
1130    /**
1131     * Sets a network's status (both internal and public) according to the update reason and
1132     * its current state.
1133     *
1134     * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
1135     * public {@link WifiConfiguration#status} field if the network is either enabled or
1136     * permanently disabled.
1137     *
1138     * @param config network to be updated.
1139     * @param reason reason code for update.
1140     * @return true if the input configuration has been updated, false otherwise.
1141     */
1142    private boolean setNetworkSelectionStatus(WifiConfiguration config, int reason) {
1143        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
1144        if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
1145            Log.e(TAG, "Invalid Network disable reason " + reason);
1146            return false;
1147        }
1148        if (reason == NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
1149            setNetworkSelectionEnabled(networkStatus);
1150            setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
1151        } else if (reason < NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
1152            setNetworkSelectionTemporarilyDisabled(networkStatus, reason);
1153        } else {
1154            setNetworkSelectionPermanentlyDisabled(networkStatus, reason);
1155            setNetworkStatus(config, WifiConfiguration.Status.DISABLED);
1156        }
1157        localLog("setNetworkSelectionStatus: configKey=" + config.configKey()
1158                + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
1159                + networkStatus.getNetworkDisableReasonString() + " at="
1160                + createDebugTimeStampString(mClock.getWallClockMillis()));
1161        saveToStore(false);
1162        return true;
1163    }
1164
1165    /**
1166     * Update a network's status (both internal and public) according to the update reason and
1167     * its current state.
1168     *
1169     * @param config network to be updated.
1170     * @param reason reason code for update.
1171     * @return true if the input configuration has been updated, false otherwise.
1172     */
1173    private boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
1174        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
1175        if (reason != NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
1176            networkStatus.incrementDisableReasonCounter(reason);
1177            // For network disable reasons, we should only update the status if we cross the
1178            // threshold.
1179            int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
1180            int disableReasonThreshold = NETWORK_SELECTION_DISABLE_THRESHOLD[reason];
1181            if (disableReasonCounter < disableReasonThreshold) {
1182                if (mVerboseLoggingEnabled) {
1183                    Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
1184                            + " for reason "
1185                            + NetworkSelectionStatus.getNetworkDisableReasonString(reason) + " is "
1186                            + networkStatus.getDisableReasonCounter(reason) + " and threshold is "
1187                            + disableReasonThreshold);
1188                }
1189                return true;
1190            }
1191        }
1192        return setNetworkSelectionStatus(config, reason);
1193    }
1194
1195    /**
1196     * Update a network's status (both internal and public) according to the update reason and
1197     * its current state.
1198     *
1199     * Each network has 2 status:
1200     * 1. NetworkSelectionStatus: This is internal selection status of the network. This is used
1201     * for temporarily disabling a network for Network Selector.
1202     * 2. Status: This is the exposed status for a network. This is mostly set by
1203     * the public API's {@link WifiManager#enableNetwork(int, boolean)} &
1204     * {@link WifiManager#disableNetwork(int)}.
1205     *
1206     * @param networkId network ID of the network that needs the update.
1207     * @param reason    reason to update the network.
1208     * @return true if the input configuration has been updated, false otherwise.
1209     */
1210    public boolean updateNetworkSelectionStatus(int networkId, int reason) {
1211        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1212        if (config == null) {
1213            return false;
1214        }
1215        return updateNetworkSelectionStatus(config, reason);
1216    }
1217
1218    /**
1219     * Attempt to re-enable a network for network selection, if this network was either:
1220     * a) Previously temporarily disabled, but its disable timeout has expired, or
1221     * b) Previously disabled because of a user switch, but is now visible to the current
1222     * user.
1223     *
1224     * @param config configuration for the network to be re-enabled for network selection. The
1225     *               network corresponding to the config must be visible to the current user.
1226     * @return true if the network identified by {@param config} was re-enabled for qualified
1227     * network selection, false otherwise.
1228     */
1229    private boolean tryEnableNetwork(WifiConfiguration config) {
1230        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
1231        if (networkStatus.isNetworkTemporaryDisabled()) {
1232            long timeDifferenceMs =
1233                    mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime();
1234            int disableReason = networkStatus.getNetworkSelectionDisableReason();
1235            long disableTimeoutMs = NETWORK_SELECTION_DISABLE_TIMEOUT_MS[disableReason];
1236            if (timeDifferenceMs >= disableTimeoutMs) {
1237                return updateNetworkSelectionStatus(
1238                        config, NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
1239            }
1240        } else if (networkStatus.isDisabledByReason(
1241                NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH)) {
1242            return updateNetworkSelectionStatus(
1243                    config, NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
1244        }
1245        return false;
1246    }
1247
1248    /**
1249     * Attempt to re-enable a network for network selection, if this network was either:
1250     * a) Previously temporarily disabled, but its disable timeout has expired, or
1251     * b) Previously disabled because of a user switch, but is now visible to the current
1252     * user.
1253     *
1254     * @param networkId the id of the network to be checked for possible unblock (due to timeout)
1255     * @return true if the network identified by {@param networkId} was re-enabled for qualified
1256     * network selection, false otherwise.
1257     */
1258    public boolean tryEnableNetwork(int networkId) {
1259        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1260        if (config == null) {
1261            return false;
1262        }
1263        return tryEnableNetwork(config);
1264    }
1265
1266    /**
1267     * Enable a network using the public {@link WifiManager#enableNetwork(int, boolean)} API.
1268     *
1269     * @param networkId     network ID of the network that needs the update.
1270     * @param disableOthers Whether to disable all other networks or not. This is used to indicate
1271     *                      that the app requested connection to a specific network.
1272     * @param uid           uid of the app requesting the update.
1273     * @return true if it succeeds, false otherwise
1274     */
1275    public boolean enableNetwork(int networkId, boolean disableOthers, int uid) {
1276        if (mVerboseLoggingEnabled) {
1277            Log.v(TAG, "Enabling network " + networkId + " (disableOthers " + disableOthers + ")");
1278        }
1279        if (!doesUidBelongToCurrentUser(uid)) {
1280            Log.e(TAG, "UID " + uid + " not visible to the current user");
1281            return false;
1282        }
1283        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1284        if (config == null) {
1285            return false;
1286        }
1287        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
1288            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
1289                    + config.configKey());
1290            return false;
1291        }
1292        if (!updateNetworkSelectionStatus(
1293                networkId, WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)) {
1294            return false;
1295        }
1296        if (disableOthers) {
1297            setLastSelectedNetwork(networkId);
1298        }
1299        return true;
1300    }
1301
1302    /**
1303     * Disable a network using the public {@link WifiManager#disableNetwork(int)} API.
1304     *
1305     * @param networkId network ID of the network that needs the update.
1306     * @param uid       uid of the app requesting the update.
1307     * @return true if it succeeds, false otherwise
1308     */
1309    public boolean disableNetwork(int networkId, int uid) {
1310        if (mVerboseLoggingEnabled) {
1311            Log.v(TAG, "Disabling network " + networkId);
1312        }
1313        if (!doesUidBelongToCurrentUser(uid)) {
1314            Log.e(TAG, "UID " + uid + " not visible to the current user");
1315            return false;
1316        }
1317        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1318        if (config == null) {
1319            return false;
1320        }
1321        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
1322            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
1323                    + config.configKey());
1324            return false;
1325        }
1326        if (!updateNetworkSelectionStatus(
1327                networkId, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER)) {
1328            return false;
1329        }
1330        if (networkId == mLastSelectedNetworkId) {
1331            clearLastSelectedNetwork();
1332        }
1333        return true;
1334    }
1335
1336    /**
1337     * Checks if the |uid| has the necessary permission to force a connection to a network
1338     * and updates the last connected UID for the provided configuration.
1339     *
1340     * @param networkId network ID corresponding to the network.
1341     * @param uid       uid of the app requesting the connection.
1342     * @return true if |uid| has the necessary permission to trigger explicit connection to the
1343     * network, false otherwise.
1344     * Note: This returns true only for the system settings/sysui app which holds the
1345     * {@link android.Manifest.permission#OVERRIDE_WIFI_CONFIG} permission. We don't want to let
1346     * any other app force connection to a network.
1347     */
1348    public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
1349        if (mVerboseLoggingEnabled) {
1350            Log.v(TAG, "Update network last connect UID for " + networkId);
1351        }
1352        if (!doesUidBelongToCurrentUser(uid)) {
1353            Log.e(TAG, "UID " + uid + " not visible to the current user");
1354            return false;
1355        }
1356        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1357        if (config == null) {
1358            return false;
1359        }
1360        if (!canModifyNetwork(config, uid, ALLOW_LOCKDOWN_CHECK_BYPASS)) {
1361            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
1362                    + config.configKey());
1363            return false;
1364        }
1365        config.lastConnectUid = uid;
1366        return true;
1367    }
1368
1369    /**
1370     * Updates a network configuration after a successful connection to it.
1371     *
1372     * This method updates the following WifiConfiguration elements:
1373     * 1. Set the |lastConnected| timestamp.
1374     * 2. Increment |numAssociation| counter.
1375     * 3. Clear the disable reason counters in the associated |NetworkSelectionStatus|.
1376     * 4. Set the hasEverConnected| flag in the associated |NetworkSelectionStatus|.
1377     * 5. Sets the status of network as |CURRENT|.
1378     *
1379     * @param networkId network ID corresponding to the network.
1380     * @return true if the network was found, false otherwise.
1381     */
1382    public boolean updateNetworkAfterConnect(int networkId) {
1383        if (mVerboseLoggingEnabled) {
1384            Log.v(TAG, "Update network after connect for " + networkId);
1385        }
1386        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1387        if (config == null) {
1388            return false;
1389        }
1390        config.lastConnected = mClock.getWallClockMillis();
1391        config.numAssociation++;
1392        config.getNetworkSelectionStatus().clearDisableReasonCounter();
1393        config.getNetworkSelectionStatus().setHasEverConnected(true);
1394        setNetworkStatus(config, WifiConfiguration.Status.CURRENT);
1395        saveToStore(false);
1396        return true;
1397    }
1398
1399    /**
1400     * Updates a network configuration after disconnection from it.
1401     *
1402     * This method updates the following WifiConfiguration elements:
1403     * 1. Set the |lastDisConnected| timestamp.
1404     * 2. Sets the status of network back to |ENABLED|.
1405     *
1406     * @param networkId network ID corresponding to the network.
1407     * @return true if the network was found, false otherwise.
1408     */
1409    public boolean updateNetworkAfterDisconnect(int networkId) {
1410        if (mVerboseLoggingEnabled) {
1411            Log.v(TAG, "Update network after disconnect for " + networkId);
1412        }
1413        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1414        if (config == null) {
1415            return false;
1416        }
1417        config.lastDisconnected = mClock.getWallClockMillis();
1418        // If the network hasn't been disabled, mark it back as
1419        // enabled after disconnection.
1420        if (config.status == WifiConfiguration.Status.CURRENT) {
1421            setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
1422        }
1423        saveToStore(false);
1424        return true;
1425    }
1426
1427    /**
1428     * Set default GW MAC address for the provided network.
1429     *
1430     * @param networkId  network ID corresponding to the network.
1431     * @param macAddress MAC address of the gateway to be set.
1432     * @return true if the network was found, false otherwise.
1433     */
1434    public boolean setNetworkDefaultGwMacAddress(int networkId, String macAddress) {
1435        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1436        if (config == null) {
1437            return false;
1438        }
1439        config.defaultGwMacAddress = macAddress;
1440        return true;
1441    }
1442
1443    /**
1444     * Clear the {@link NetworkSelectionStatus#mCandidate},
1445     * {@link NetworkSelectionStatus#mCandidateScore} &
1446     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
1447     *
1448     * This is invoked by Network Selector at the start of every selection procedure to clear all
1449     * configured networks' scan-result-candidates.
1450     *
1451     * @param networkId network ID corresponding to the network.
1452     * @return true if the network was found, false otherwise.
1453     */
1454    public boolean clearNetworkCandidateScanResult(int networkId) {
1455        if (mVerboseLoggingEnabled) {
1456            Log.v(TAG, "Clear network candidate scan result for " + networkId);
1457        }
1458        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1459        if (config == null) {
1460            return false;
1461        }
1462        config.getNetworkSelectionStatus().setCandidate(null);
1463        config.getNetworkSelectionStatus().setCandidateScore(Integer.MIN_VALUE);
1464        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(false);
1465        return true;
1466    }
1467
1468    /**
1469     * Set the {@link NetworkSelectionStatus#mCandidate},
1470     * {@link NetworkSelectionStatus#mCandidateScore} &
1471     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
1472     *
1473     * This is invoked by Network Selector when it sees a network during network selection procedure
1474     * to set the scan result candidate.
1475     *
1476     * @param networkId  network ID corresponding to the network.
1477     * @param scanResult Candidate ScanResult associated with this network.
1478     * @param score      Score assigned to the candidate.
1479     * @return true if the network was found, false otherwise.
1480     */
1481    public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score) {
1482        if (mVerboseLoggingEnabled) {
1483            Log.v(TAG, "Set network candidate scan result " + scanResult + " for " + networkId);
1484        }
1485        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1486        if (config == null) {
1487            return false;
1488        }
1489        config.getNetworkSelectionStatus().setCandidate(scanResult);
1490        config.getNetworkSelectionStatus().setCandidateScore(score);
1491        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
1492        return true;
1493    }
1494
1495    /**
1496     * Iterate through all the saved networks and remove the provided configuration from the
1497     * {@link NetworkSelectionStatus#mConnectChoice} from them.
1498     *
1499     * This is invoked when a network is removed from our records.
1500     *
1501     * @param connectChoiceConfigKey ConfigKey corresponding to the network that is being removed.
1502     */
1503    private void removeConnectChoiceFromAllNetworks(String connectChoiceConfigKey) {
1504        if (mVerboseLoggingEnabled) {
1505            Log.v(TAG, "Removing connect choice from all networks " + connectChoiceConfigKey);
1506        }
1507        if (connectChoiceConfigKey == null) {
1508            return;
1509        }
1510        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
1511            WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
1512            String connectChoice = status.getConnectChoice();
1513            if (TextUtils.equals(connectChoice, connectChoiceConfigKey)) {
1514                Log.d(TAG, "remove connect choice:" + connectChoice + " from " + config.SSID
1515                        + " : " + config.networkId);
1516                clearNetworkConnectChoice(config.networkId);
1517            }
1518        }
1519    }
1520
1521    /**
1522     * Clear the {@link NetworkSelectionStatus#mConnectChoice} &
1523     * {@link NetworkSelectionStatus#mConnectChoiceTimestamp} for the provided network.
1524     *
1525     * @param networkId network ID corresponding to the network.
1526     * @return true if the network was found, false otherwise.
1527     */
1528    public boolean clearNetworkConnectChoice(int networkId) {
1529        if (mVerboseLoggingEnabled) {
1530            Log.v(TAG, "Clear network connect choice for " + networkId);
1531        }
1532        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1533        if (config == null) {
1534            return false;
1535        }
1536        config.getNetworkSelectionStatus().setConnectChoice(null);
1537        config.getNetworkSelectionStatus().setConnectChoiceTimestamp(
1538                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1539        saveToStore(false);
1540        return true;
1541    }
1542
1543    /**
1544     * Set the {@link NetworkSelectionStatus#mConnectChoice} &
1545     * {@link NetworkSelectionStatus#mConnectChoiceTimestamp} for the provided network.
1546     *
1547     * This is invoked by Network Selector when the user overrides the currently connected network
1548     * choice.
1549     *
1550     * @param networkId              network ID corresponding to the network.
1551     * @param connectChoiceConfigKey ConfigKey corresponding to the network which was chosen over
1552     *                               this network.
1553     * @param timestamp              timestamp at which the choice was made.
1554     * @return true if the network was found, false otherwise.
1555     */
1556    public boolean setNetworkConnectChoice(
1557            int networkId, String connectChoiceConfigKey, long timestamp) {
1558        if (mVerboseLoggingEnabled) {
1559            Log.v(TAG, "Set network connect choice " + connectChoiceConfigKey + " for " + networkId);
1560        }
1561        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1562        if (config == null) {
1563            return false;
1564        }
1565        config.getNetworkSelectionStatus().setConnectChoice(connectChoiceConfigKey);
1566        config.getNetworkSelectionStatus().setConnectChoiceTimestamp(timestamp);
1567        saveToStore(false);
1568        return true;
1569    }
1570
1571    /**
1572     * Increments the number of no internet access reports in the provided network.
1573     *
1574     * @param networkId network ID corresponding to the network.
1575     * @return true if the network was found, false otherwise.
1576     */
1577    public boolean incrementNetworkNoInternetAccessReports(int networkId) {
1578        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1579        if (config == null) {
1580            return false;
1581        }
1582        config.numNoInternetAccessReports++;
1583        return true;
1584    }
1585
1586    /**
1587     * Sets the internet access is validated or not in the provided network.
1588     *
1589     * @param networkId network ID corresponding to the network.
1590     * @param validated Whether access is validated or not.
1591     * @return true if the network was found, false otherwise.
1592     */
1593    public boolean setNetworkValidatedInternetAccess(int networkId, boolean validated) {
1594        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1595        if (config == null) {
1596            return false;
1597        }
1598        config.validatedInternetAccess = validated;
1599        config.numNoInternetAccessReports = 0;
1600        saveToStore(false);
1601        return true;
1602    }
1603
1604    /**
1605     * Sets whether the internet access is expected or not in the provided network.
1606     *
1607     * @param networkId network ID corresponding to the network.
1608     * @param expected  Whether access is expected or not.
1609     * @return true if the network was found, false otherwise.
1610     */
1611    public boolean setNetworkNoInternetAccessExpected(int networkId, boolean expected) {
1612        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1613        if (config == null) {
1614            return false;
1615        }
1616        config.noInternetAccessExpected = expected;
1617        return true;
1618    }
1619
1620    /**
1621     * Sets the various RSSI stats in the provided network.
1622     *
1623     * @param networkId network ID corresponding to the network.
1624     * @return true if the network was found, false otherwise.
1625     */
1626    public boolean setNetworkRSSIStats(
1627            int networkId, int numUserTriggeredWifiDisableLowRSSI,
1628            int numUserTriggeredWifiDisableBadRSSI, int numUserTriggeredWifiDisableNotHighRSSI,
1629            int numTicksAtLowRSSI, int numTicksAtBadRSSI, int numTicksAtNotHighRSSI) {
1630        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
1631        if (config == null) {
1632            return false;
1633        }
1634        config.numUserTriggeredWifiDisableLowRSSI = numUserTriggeredWifiDisableLowRSSI;
1635        config.numUserTriggeredWifiDisableBadRSSI = numUserTriggeredWifiDisableBadRSSI;
1636        config.numUserTriggeredWifiDisableNotHighRSSI = numUserTriggeredWifiDisableNotHighRSSI;
1637        config.numTicksAtLowRSSI = numTicksAtLowRSSI;
1638        config.numTicksAtBadRSSI = numTicksAtBadRSSI;
1639        config.numTicksAtNotHighRSSI = numTicksAtNotHighRSSI;
1640        return true;
1641    }
1642
1643    /**
1644     * Helper method to clear out the {@link #mNextNetworkId} user/app network selection. This
1645     * is done when either the corresponding network is either removed or disabled.
1646     */
1647    private void clearLastSelectedNetwork() {
1648        if (mVerboseLoggingEnabled) {
1649            Log.v(TAG, "Clearing last selected network");
1650        }
1651        mLastSelectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
1652        mLastSelectedTimeStamp = NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
1653    }
1654
1655    /**
1656     * Helper method to mark a network as the last selected one by an app/user. This is set
1657     * when an app invokes {@link #enableNetwork(int, boolean, int)} with |disableOthers| flag set.
1658     * This is used by network selector to assign a special bonus during network selection.
1659     */
1660    private void setLastSelectedNetwork(int networkId) {
1661        if (mVerboseLoggingEnabled) {
1662            Log.v(TAG, "Setting last selected network to " + networkId);
1663        }
1664        mLastSelectedNetworkId = networkId;
1665        mLastSelectedTimeStamp = mClock.getElapsedSinceBootMillis();
1666    }
1667
1668    /**
1669     * Retrieve the network Id corresponding to the last network that was explicitly selected by
1670     * an app/user.
1671     *
1672     * @return network Id corresponding to the last selected network.
1673     */
1674    public int getLastSelectedNetwork() {
1675        return mLastSelectedNetworkId;
1676    }
1677
1678    /**
1679     * Retrieve the configKey corresponding to the last network that was explicitly selected by
1680     * an app/user.
1681     *
1682     * @return network Id corresponding to the last selected network.
1683     */
1684    public String getLastSelectedNetworkConfigKey() {
1685        WifiConfiguration config = getInternalConfiguredNetwork(mLastSelectedNetworkId);
1686        if (config == null) {
1687            return "";
1688        }
1689        return config.configKey();
1690    }
1691
1692    /**
1693     * Retrieve the time stamp at which a network was explicitly selected by an app/user.
1694     *
1695     * @return timestamp in milliseconds from boot when this was set.
1696     */
1697    public long getLastSelectedTimeStamp() {
1698        return mLastSelectedTimeStamp;
1699    }
1700
1701    /**
1702     * Helper method to get the scan detail cache entry {@link #mScanDetailCaches} for the provided
1703     * network.
1704     *
1705     * @param networkId network ID corresponding to the network.
1706     * @return existing {@link ScanDetailCache} entry if one exists or null.
1707     */
1708    public ScanDetailCache getScanDetailCacheForNetwork(int networkId) {
1709        return mScanDetailCaches.get(networkId);
1710    }
1711
1712    /**
1713     * Helper method to get or create a scan detail cache entry {@link #mScanDetailCaches} for
1714     * the provided network.
1715     *
1716     * @param config configuration corresponding to the the network.
1717     * @return existing {@link ScanDetailCache} entry if one exists or a new instance created for
1718     * this network.
1719     */
1720    private ScanDetailCache getOrCreateScanDetailCacheForNetwork(WifiConfiguration config) {
1721        if (config == null) return null;
1722        ScanDetailCache cache = getScanDetailCacheForNetwork(config.networkId);
1723        if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
1724            cache = new ScanDetailCache(
1725                    config, SCAN_CACHE_ENTRIES_MAX_SIZE, SCAN_CACHE_ENTRIES_TRIM_SIZE);
1726            mScanDetailCaches.put(config.networkId, cache);
1727        }
1728        return cache;
1729    }
1730
1731    /**
1732     * Saves the provided ScanDetail into the corresponding scan detail cache entry
1733     * {@link #mScanDetailCaches} for the provided network.
1734     *
1735     * @param config     configuration corresponding to the the network.
1736     * @param scanDetail new scan detail instance to be saved into the cache.
1737     */
1738    private void saveToScanDetailCacheForNetwork(
1739            WifiConfiguration config, ScanDetail scanDetail) {
1740        ScanResult scanResult = scanDetail.getScanResult();
1741
1742        ScanDetailCache scanDetailCache = getOrCreateScanDetailCacheForNetwork(config);
1743        if (scanDetailCache == null) {
1744            Log.e(TAG, "Could not allocate scan cache for " + config.getPrintableSsid());
1745            return;
1746        }
1747
1748        // Adding a new BSSID
1749        ScanResult result = scanDetailCache.get(scanResult.BSSID);
1750        if (result != null) {
1751            // transfer the black list status
1752            scanResult.blackListTimestamp = result.blackListTimestamp;
1753            scanResult.numIpConfigFailures = result.numIpConfigFailures;
1754            scanResult.numConnection = result.numConnection;
1755        }
1756        if (config.ephemeral) {
1757            // For an ephemeral Wi-Fi config, the ScanResult should be considered
1758            // untrusted.
1759            scanResult.untrusted = true;
1760        }
1761
1762        // Add the scan detail to this network's scan detail cache.
1763        scanDetailCache.put(scanDetail);
1764
1765        // Since we added a scan result to this configuration, re-attempt linking.
1766        // TODO: Do we really need to do this after every scan result?
1767        attemptNetworkLinking(config);
1768    }
1769
1770    /**
1771     * Retrieves a saved network corresponding to the provided scan detail if one exists.
1772     *
1773     * @param scanDetail ScanDetail instance  to use for looking up the network.
1774     * @return WifiConfiguration object representing the network corresponding to the scanDetail,
1775     * null if none exists.
1776     */
1777    private WifiConfiguration getSavedNetworkForScanDetail(ScanDetail scanDetail) {
1778        ScanResult scanResult = scanDetail.getScanResult();
1779        if (scanResult == null) {
1780            Log.e(TAG, "No scan result found in scan detail");
1781            return null;
1782        }
1783        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
1784            if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config)) {
1785                if (mVerboseLoggingEnabled) {
1786                    Log.v(TAG, "getSavedNetworkFromScanDetail Found " + config.configKey()
1787                            + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
1788                }
1789                return config;
1790            }
1791        }
1792        return null;
1793    }
1794
1795    /**
1796     * Retrieves a saved network corresponding to the provided scan detail if one exists and caches
1797     * the provided |scanDetail| into the corresponding scan detail cache entry
1798     * {@link #mScanDetailCaches} for the retrieved network.
1799     *
1800     * @param scanDetail input a scanDetail from the scan result
1801     * @return WifiConfiguration object representing the network corresponding to the scanDetail,
1802     * null if none exists.
1803     */
1804    public WifiConfiguration getSavedNetworkForScanDetailAndCache(ScanDetail scanDetail) {
1805        WifiConfiguration network = getSavedNetworkForScanDetail(scanDetail);
1806        if (network == null) {
1807            return null;
1808        }
1809        saveToScanDetailCacheForNetwork(network, scanDetail);
1810        // Cache DTIM values parsed from the beacon frame Traffic Indication Map (TIM)
1811        // Information Element (IE), into the associated WifiConfigurations. Most of the
1812        // time there is no TIM IE in the scan result (Probe Response instead of Beacon
1813        // Frame), these scanResult DTIM's are negative and ignored.
1814        // Used for metrics collection.
1815        if (scanDetail.getNetworkDetail() != null
1816                && scanDetail.getNetworkDetail().getDtimInterval() > 0) {
1817            network.dtimInterval = scanDetail.getNetworkDetail().getDtimInterval();
1818        }
1819        return createExternalWifiConfiguration(network, true);
1820    }
1821
1822    /**
1823     * Update the scan detail cache associated with current connected network with latest
1824     * RSSI value in the provided WifiInfo.
1825     * This is invoked when we get an RSSI poll update after connection.
1826     *
1827     * @param info WifiInfo instance pointing to the current connected network.
1828     */
1829    public void updateScanDetailCacheFromWifiInfo(WifiInfo info) {
1830        WifiConfiguration config = getInternalConfiguredNetwork(info.getNetworkId());
1831        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(info.getNetworkId());
1832        if (config != null && scanDetailCache != null) {
1833            ScanDetail scanDetail = scanDetailCache.getScanDetail(info.getBSSID());
1834            if (scanDetail != null) {
1835                ScanResult result = scanDetail.getScanResult();
1836                long previousSeen = result.seen;
1837                int previousRssi = result.level;
1838                // Update the scan result
1839                scanDetail.setSeen();
1840                result.level = info.getRssi();
1841                // Average the RSSI value
1842                result.averageRssi(previousRssi, previousSeen, SCAN_RESULT_MAXIMUM_AGE_MS);
1843                if (mVerboseLoggingEnabled) {
1844                    Log.v(TAG, "Updating scan detail cache freq=" + result.frequency
1845                            + " BSSID=" + result.BSSID
1846                            + " RSSI=" + result.level
1847                            + " for " + config.configKey());
1848                }
1849            }
1850        }
1851    }
1852
1853    /**
1854     * Helper method to check if the 2 provided networks can be linked or not.
1855     * Networks are considered for linking if:
1856     * 1. Share the same GW MAC address.
1857     * 2. Scan results for the networks have AP's with MAC address which differ only in the last
1858     * nibble.
1859     *
1860     * @param network1         WifiConfiguration corresponding to network 1.
1861     * @param network2         WifiConfiguration corresponding to network 2.
1862     * @param scanDetailCache1 ScanDetailCache entry for network 1.
1863     * @param scanDetailCache1 ScanDetailCache entry for network 2.
1864     * @return true if the networks should be linked, false if the networks should be unlinked.
1865     */
1866    private boolean shouldNetworksBeLinked(
1867            WifiConfiguration network1, WifiConfiguration network2,
1868            ScanDetailCache scanDetailCache1, ScanDetailCache scanDetailCache2) {
1869        // TODO (b/30706406): Link networks only with same passwords if the
1870        // |mOnlyLinkSameCredentialConfigurations| flag is set.
1871        if (mOnlyLinkSameCredentialConfigurations) {
1872            if (!TextUtils.equals(network1.preSharedKey, network2.preSharedKey)) {
1873                if (mVerboseLoggingEnabled) {
1874                    Log.v(TAG, "shouldNetworksBeLinked unlink due to password mismatch");
1875                }
1876                return false;
1877            }
1878        }
1879        if (network1.defaultGwMacAddress != null && network2.defaultGwMacAddress != null) {
1880            // If both default GW are known, link only if they are equal
1881            if (network1.defaultGwMacAddress.equals(network2.defaultGwMacAddress)) {
1882                if (mVerboseLoggingEnabled) {
1883                    Log.v(TAG, "shouldNetworksBeLinked link due to same gw " + network2.SSID
1884                            + " and " + network1.SSID + " GW " + network1.defaultGwMacAddress);
1885                }
1886                return true;
1887            }
1888        } else {
1889            // We do not know BOTH default gateways hence we will try to link
1890            // hoping that WifiConfigurations are indeed behind the same gateway.
1891            // once both WifiConfiguration have been tried and thus once both default gateways
1892            // are known we will revisit the choice of linking them.
1893            if (scanDetailCache1 != null && scanDetailCache2 != null) {
1894                for (String abssid : scanDetailCache1.keySet()) {
1895                    for (String bbssid : scanDetailCache2.keySet()) {
1896                        if (abssid.regionMatches(
1897                                true, 0, bbssid, 0, LINK_CONFIGURATION_BSSID_MATCH_LENGTH)) {
1898                            // If first 16 ASCII characters of BSSID matches,
1899                            // we assume this is a DBDC.
1900                            if (mVerboseLoggingEnabled) {
1901                                Log.v(TAG, "shouldNetworksBeLinked link due to DBDC BSSID match "
1902                                        + network2.SSID + " and " + network1.SSID
1903                                        + " bssida " + abssid + " bssidb " + bbssid);
1904                            }
1905                            return true;
1906                        }
1907                    }
1908                }
1909            }
1910        }
1911        return false;
1912    }
1913
1914    /**
1915     * Helper methods to link 2 networks together.
1916     *
1917     * @param network1 WifiConfiguration corresponding to network 1.
1918     * @param network2 WifiConfiguration corresponding to network 2.
1919     */
1920    private void linkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
1921        if (mVerboseLoggingEnabled) {
1922            Log.v(TAG, "linkNetworks will link " + network2.configKey()
1923                    + " and " + network1.configKey());
1924        }
1925        if (network2.linkedConfigurations == null) {
1926            network2.linkedConfigurations = new HashMap<>();
1927        }
1928        if (network1.linkedConfigurations == null) {
1929            network1.linkedConfigurations = new HashMap<>();
1930        }
1931        // TODO (b/30638473): This needs to become a set instead of map, but it will need
1932        // public interface changes and need some migration of existing store data.
1933        network2.linkedConfigurations.put(network1.configKey(), 1);
1934        network1.linkedConfigurations.put(network2.configKey(), 1);
1935    }
1936
1937    /**
1938     * Helper methods to unlink 2 networks from each other.
1939     *
1940     * @param network1 WifiConfiguration corresponding to network 1.
1941     * @param network2 WifiConfiguration corresponding to network 2.
1942     */
1943    private void unlinkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
1944        if (network2.linkedConfigurations != null
1945                && (network2.linkedConfigurations.get(network1.configKey()) != null)) {
1946            if (mVerboseLoggingEnabled) {
1947                Log.v(TAG, "unlinkNetworks un-link " + network1.configKey()
1948                        + " from " + network2.configKey());
1949            }
1950            network2.linkedConfigurations.remove(network1.configKey());
1951        }
1952        if (network1.linkedConfigurations != null
1953                && (network1.linkedConfigurations.get(network2.configKey()) != null)) {
1954            if (mVerboseLoggingEnabled) {
1955                Log.v(TAG, "unlinkNetworks un-link " + network2.configKey()
1956                        + " from " + network1.configKey());
1957            }
1958            network1.linkedConfigurations.remove(network2.configKey());
1959        }
1960    }
1961
1962    /**
1963     * This method runs through all the saved networks and checks if the provided network can be
1964     * linked with any of them.
1965     *
1966     * @param config WifiConfiguration object corresponding to the network that needs to be
1967     *               checked for potential links.
1968     */
1969    private void attemptNetworkLinking(WifiConfiguration config) {
1970        // Only link WPA_PSK config.
1971        if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
1972            return;
1973        }
1974        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
1975        // Ignore configurations with large number of BSSIDs.
1976        if (scanDetailCache != null
1977                && scanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
1978            return;
1979        }
1980        for (WifiConfiguration linkConfig : getInternalConfiguredNetworks()) {
1981            if (linkConfig.configKey().equals(config.configKey())) {
1982                continue;
1983            }
1984            if (linkConfig.ephemeral) {
1985                continue;
1986            }
1987            // Network Selector will be allowed to dynamically jump from a linked configuration
1988            // to another, hence only link configurations that have WPA_PSK security type.
1989            if (!linkConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
1990                continue;
1991            }
1992            ScanDetailCache linkScanDetailCache =
1993                    getScanDetailCacheForNetwork(linkConfig.networkId);
1994            // Ignore configurations with large number of BSSIDs.
1995            if (linkScanDetailCache != null
1996                    && linkScanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
1997                continue;
1998            }
1999            // Check if the networks should be linked/unlinked.
2000            if (shouldNetworksBeLinked(
2001                    config, linkConfig, scanDetailCache, linkScanDetailCache)) {
2002                linkNetworks(config, linkConfig);
2003            } else {
2004                unlinkNetworks(config, linkConfig);
2005            }
2006        }
2007    }
2008
2009    /**
2010     * Helper method to fetch list of channels for a network from the associated ScanResult's cache
2011     * and add it to the provided channel as long as the size of the set is less than
2012     * |maxChannelSetSize|.
2013     *
2014     * @param channelSet        Channel set holding all the channels for the network.
2015     * @param scanDetailCache   ScanDetailCache entry associated with the network.
2016     * @param nowInMillis       current timestamp to be used for age comparison.
2017     * @param ageInMillis       only consider scan details whose timestamps are earlier than this
2018     *                          value.
2019     * @param maxChannelSetSize Maximum number of channels to be added to the set.
2020     * @return false if the list is full, true otherwise.
2021     */
2022    private boolean addToChannelSetForNetworkFromScanDetailCache(
2023            Set<Integer> channelSet, ScanDetailCache scanDetailCache,
2024            long nowInMillis, long ageInMillis, int maxChannelSetSize) {
2025        if (scanDetailCache != null && scanDetailCache.size() > 0) {
2026            for (ScanDetail scanDetail : scanDetailCache.values()) {
2027                ScanResult result = scanDetail.getScanResult();
2028                boolean valid = (nowInMillis - result.seen) < ageInMillis;
2029                if (mVerboseLoggingEnabled) {
2030                    Log.v(TAG, "fetchChannelSetForNetwork has " + result.BSSID + " freq "
2031                            + result.frequency + " age " + (nowInMillis - result.seen)
2032                            + " ?=" + valid);
2033                }
2034                if (valid) {
2035                    channelSet.add(result.frequency);
2036                }
2037                if (channelSet.size() >= maxChannelSetSize) {
2038                    return false;
2039                }
2040            }
2041        }
2042        return true;
2043    }
2044
2045    /**
2046     * Retrieve a set of channels on which AP's for the provided network was seen using the
2047     * internal ScanResult's cache {@link #mScanDetailCaches}. This is used for initiating partial
2048     * scans for the currently connected network.
2049     *
2050     * @param networkId   network ID corresponding to the network.
2051     * @param ageInMillis only consider scan details whose timestamps are earlier than this value.
2052     * @return Set containing the frequencies on which this network was found, null if the network
2053     * was not found or there are no associated scan details in the cache.
2054     */
2055    public Set<Integer> fetchChannelSetForNetworkForPartialScan(int networkId, long ageInMillis) {
2056        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
2057        if (config == null) {
2058            return null;
2059        }
2060        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(networkId);
2061        if (scanDetailCache == null && config.linkedConfigurations == null) {
2062            Log.i(TAG, "No scan detail and linked configs associated with networkId " + networkId);
2063            return null;
2064        }
2065        if (mVerboseLoggingEnabled) {
2066            StringBuilder dbg = new StringBuilder();
2067            dbg.append("fetchChannelSetForNetworkForPartialScan ageInMillis ")
2068                    .append(ageInMillis)
2069                    .append(" for ")
2070                    .append(config.configKey())
2071                    .append(" max ")
2072                    .append(mMaxNumActiveChannelsForPartialScans);
2073            if (scanDetailCache != null) {
2074                dbg.append(" bssids " + scanDetailCache.size());
2075            }
2076            if (config.linkedConfigurations != null) {
2077                dbg.append(" linked " + config.linkedConfigurations.size());
2078            }
2079            Log.v(TAG, dbg.toString());
2080        }
2081        Set<Integer> channelSet = new HashSet<>();
2082        long nowInMillis = mClock.getWallClockMillis();
2083
2084        // First get channels for the network.
2085        if (!addToChannelSetForNetworkFromScanDetailCache(
2086                channelSet, scanDetailCache, nowInMillis, ageInMillis,
2087                mMaxNumActiveChannelsForPartialScans)) {
2088            return channelSet;
2089        }
2090
2091        // Now get channels for linked networks.
2092        if (config.linkedConfigurations != null) {
2093            for (String configKey : config.linkedConfigurations.keySet()) {
2094                WifiConfiguration linkedConfig = getInternalConfiguredNetwork(configKey);
2095                if (linkedConfig == null) {
2096                    continue;
2097                }
2098                ScanDetailCache linkedScanDetailCache =
2099                        getScanDetailCacheForNetwork(linkedConfig.networkId);
2100                if (!addToChannelSetForNetworkFromScanDetailCache(
2101                        channelSet, linkedScanDetailCache, nowInMillis, ageInMillis,
2102                        mMaxNumActiveChannelsForPartialScans)) {
2103                    break;
2104                }
2105            }
2106        }
2107        return channelSet;
2108    }
2109
2110    /**
2111     * Retrieves a list of all the saved networks before enabling disconnected/connected PNO.
2112     *
2113     * PNO network list sent to the firmware has limited size. If there are a lot of saved
2114     * networks, this list will be truncated and we might end up not sending the networks
2115     * with the highest chance of connecting to the firmware.
2116     * So, re-sort the network list based on the frequency of connection to those networks
2117     * and whether it was last seen in the scan results.
2118     *
2119     * TODO (b/30399964): Recalculate the list whenever network status changes.
2120     * @return list of networks with updated priorities.
2121     */
2122    public List<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList() {
2123        List<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
2124        List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
2125        // Remove any permanently disabled networks.
2126        Iterator<WifiConfiguration> iter = networks.iterator();
2127        while (iter.hasNext()) {
2128            WifiConfiguration config = iter.next();
2129            if (config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
2130                iter.remove();
2131            }
2132        }
2133        Collections.sort(networks, sScanListComparator);
2134        // Let's use the network list size - 1 as the highest priority and then go down from there.
2135        // So, the most frequently connected network has the highest priority now.
2136        int priority = networks.size() - 1;
2137        for (WifiConfiguration config : networks) {
2138            pnoList.add(WifiConfigurationUtil.createPnoNetwork(config, priority));
2139            priority--;
2140        }
2141        return pnoList;
2142    }
2143
2144    /**
2145     * Retrieves a list of all the saved hidden networks for scans.
2146     *
2147     * Hidden network list sent to the firmware has limited size. If there are a lot of saved
2148     * networks, this list will be truncated and we might end up not sending the networks
2149     * with the highest chance of connecting to the firmware.
2150     * So, re-sort the network list based on the frequency of connection to those networks
2151     * and whether it was last seen in the scan results.
2152     *
2153     * @return list of networks with updated priorities.
2154     */
2155    public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList() {
2156        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenList = new ArrayList<>();
2157        List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
2158        // Remove any permanently disabled networks or non hidden networks.
2159        Iterator<WifiConfiguration> iter = networks.iterator();
2160        while (iter.hasNext()) {
2161            WifiConfiguration config = iter.next();
2162            if (!config.hiddenSSID ||
2163                    config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
2164                iter.remove();
2165            }
2166        }
2167        Collections.sort(networks, sScanListComparator);
2168        // Let's use the network list size - 1 as the highest priority and then go down from there.
2169        // So, the most frequently connected network has the highest priority now.
2170        int priority = networks.size() - 1;
2171        for (WifiConfiguration config : networks) {
2172            hiddenList.add(
2173                    new WifiScanner.ScanSettings.HiddenNetwork(config.SSID));
2174            priority--;
2175        }
2176        return hiddenList;
2177    }
2178
2179    /**
2180     * Check if the provided ephemeral network was deleted by the user or not.
2181     *
2182     * @param ssid caller must ensure that the SSID passed thru this API match
2183     *             the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
2184     * @return true if network was deleted, false otherwise.
2185     */
2186    public boolean wasEphemeralNetworkDeleted(String ssid) {
2187        return mDeletedEphemeralSSIDs.contains(ssid);
2188    }
2189
2190    /**
2191     * Disable an ephemeral SSID for the purpose of network selection.
2192     *
2193     * The only way to "un-disable it" is if the user create a network for that SSID and then
2194     * forget it.
2195     *
2196     * @param ssid caller must ensure that the SSID passed thru this API match
2197     *             the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
2198     * @return the {@link WifiConfiguration} corresponding to this SSID, if any, so that we can
2199     * disconnect if this is the current network.
2200     */
2201    public WifiConfiguration disableEphemeralNetwork(String ssid) {
2202        if (ssid == null) {
2203            return null;
2204        }
2205        WifiConfiguration foundConfig = null;
2206        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
2207            if (config.ephemeral && TextUtils.equals(config.SSID, ssid)) {
2208                foundConfig = config;
2209                break;
2210            }
2211        }
2212        mDeletedEphemeralSSIDs.add(ssid);
2213        Log.d(TAG, "Forget ephemeral SSID " + ssid + " num=" + mDeletedEphemeralSSIDs.size());
2214        if (foundConfig != null) {
2215            Log.d(TAG, "Found ephemeral config in disableEphemeralNetwork: "
2216                    + foundConfig.networkId);
2217        }
2218        return foundConfig;
2219    }
2220
2221    /**
2222     * Resets all sim networks state.
2223     */
2224    public void resetSimNetworks() {
2225        if (mVerboseLoggingEnabled) localLog("resetSimNetworks");
2226        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
2227            if (TelephonyUtil.isSimConfig(config)) {
2228                String currentIdentity = TelephonyUtil.getSimIdentity(mTelephonyManager,
2229                        config.enterpriseConfig.getEapMethod());
2230                // Update the loaded config
2231                config.enterpriseConfig.setIdentity(currentIdentity);
2232                config.enterpriseConfig.setAnonymousIdentity("");
2233            }
2234        }
2235    }
2236
2237    /**
2238     * Any network using certificates to authenticate access requires unlocked key store; unless
2239     * the certificates can be stored with hardware encryption
2240     *
2241     * @return true if we need an unlocked keystore, false otherwise.
2242     */
2243    public boolean needsUnlockedKeyStore() {
2244        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
2245            if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
2246                    && config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
2247                if (mWifiKeyStore.needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
2248                    return true;
2249                }
2250            }
2251        }
2252        return false;
2253    }
2254
2255    /**
2256     * Helper method to clear internal databases.
2257     * This method clears the:
2258     *  - List of configured networks.
2259     *  - Map of scan detail caches.
2260     *  - List of deleted ephemeral networks.
2261     */
2262    private void clearInternalData() {
2263        mConfiguredNetworks.clear();
2264        mDeletedEphemeralSSIDs.clear();
2265        mScanDetailCaches.clear();
2266        clearLastSelectedNetwork();
2267    }
2268
2269    /**
2270     * Helper method to perform the following operations during user switch:
2271     * - Load from the new store files.
2272     * - Save the store files again to migrate any user specific networks from the shared store
2273     *   to user store.
2274     *
2275     * @param userId The identifier of the new foreground user, after the switch.
2276     */
2277    private void loadFromStoreAndMigrateAfterUserSwitch(int userId) {
2278        // Switch out the user store file.
2279        mWifiConfigStore.switchUserStore(mWifiConfigStore.createUserFile(userId));
2280        if (loadFromStore()) {
2281            saveToStore(true);
2282        }
2283    }
2284
2285    /**
2286     * Handles the switch to a different foreground user:
2287     * - Flush the current state to the old user's store file.
2288     * - Switch the user specific store file.
2289     * - Reload the networks from the store files (shared & user).
2290     * - Write the store files to move any user specific private networks from shared store to user
2291     *   store.
2292     *
2293     * Need to be called when {@link com.android.server.SystemService#onSwitchUser(int)} is invoked.
2294     *
2295     * @param userId The identifier of the new foreground user, after the switch.
2296     */
2297    public void handleUserSwitch(int userId) {
2298        if (userId == mCurrentUserId) {
2299            Log.w(TAG, "User already in foreground " + userId);
2300            return;
2301        }
2302        if (mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
2303            saveToStore(true);
2304        }
2305        mCurrentUserId = userId;
2306        mConfiguredNetworks.setNewUser(userId);
2307        clearInternalData();
2308
2309        if (mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
2310            loadFromStoreAndMigrateAfterUserSwitch(mCurrentUserId);
2311        } else {
2312            // Since the new user is not logged-in yet, we cannot read data from the store files
2313            // yet.
2314            Log.i(TAG, "Waiting for user unlock to load from store");
2315        }
2316    }
2317
2318    /**
2319     * Handles the unlock of foreground user. This maybe needed to read the store file if the user's
2320     * CE storage is not visible when {@link #handleUserSwitch(int)} is invoked.
2321     *
2322     * Need to be called when {@link com.android.server.SystemService#onUnlockUser(int)} is invoked.
2323     *
2324     * @param userId The identifier of the user that unlocked.
2325     */
2326    public void handleUserUnlock(int userId) {
2327        if (userId == mCurrentUserId) {
2328            loadFromStoreAndMigrateAfterUserSwitch(mCurrentUserId);
2329        }
2330    }
2331
2332    /**
2333     * Handles the stop of foreground user. This is needed to write the store file to flush
2334     * out any pending data before the user's CE store storage is unavailable.
2335     *
2336     * Need to be called when {@link com.android.server.SystemService#onStopUser(int)} is invoked.
2337     *
2338     * @param userId The identifier of the user that stopped.
2339     */
2340    public void handleUserStop(int userId) {
2341        if (userId == mCurrentUserId && mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
2342            saveToStore(true);
2343            clearInternalData();
2344            mCurrentUserId = UserHandle.USER_SYSTEM;
2345        }
2346    }
2347
2348    /**
2349     * Helper function to populate the internal (in-memory) data from the retrieved store (file)
2350     * data. It also sends out the networks changed broadcast after loading all the data.
2351     *
2352     * @param configurations        list of configurations retrieved from store.
2353     * @param deletedEphemeralSSIDs list of ssid's representing the ephemeral networks deleted by
2354     *                              the user.
2355     */
2356    private void loadInternalData(
2357            List<WifiConfiguration> configurations, Set<String> deletedEphemeralSSIDs) {
2358        // Clear out all the existing in-memory lists and load the lists from what was retrieved
2359        // from the config store.
2360        clearInternalData();
2361        for (WifiConfiguration configuration : configurations) {
2362            configuration.networkId = mNextNetworkId++;
2363            if (mVerboseLoggingEnabled) {
2364                Log.v(TAG, "Adding network from store " + configuration.configKey());
2365            }
2366            mConfiguredNetworks.put(configuration);
2367        }
2368        for (String ssid : deletedEphemeralSSIDs) {
2369            mDeletedEphemeralSSIDs.add(ssid);
2370        }
2371        if (mConfiguredNetworks.sizeForAllUsers() == 0) {
2372            Log.w(TAG, "No stored networks found.");
2373        }
2374        sendConfiguredNetworksChangedBroadcast();
2375    }
2376
2377    /**
2378     * Migrate data from legacy store files. The function performs the following operations:
2379     * 1. Check if the legacy store files are present.
2380     * 2. If yes, read all the data from the store files.
2381     * 3. Save it to the new store files.
2382     * 4. Delete the legacy store file.
2383     *
2384     * @return true if migration was successful or not needed (fresh install), false if it failed.
2385     */
2386    private boolean migrateFromLegacyStore() {
2387        if (mWifiConfigStoreLegacy.areStoresPresent()) {
2388            WifiConfigStoreDataLegacy storeData = mWifiConfigStoreLegacy.read();
2389            Log.d(TAG, "Reading from legacy store completed");
2390            loadInternalData(storeData.getConfigurations(), storeData.getDeletedEphemeralSSIDs());
2391            if (!saveToStore(true)) {
2392                return false;
2393            }
2394            // TODO: Remove the legacy store files
2395            // mWifiConfigStoreLegacy.removeStores();
2396            Log.d(TAG, "Migration from legacy store completed");
2397        }
2398        return true;
2399    }
2400
2401    /**
2402     * Read the config store and load the in-memory lists from the store data retrieved and sends
2403     * out the networks changed broadcast. This method first checks if there is any data to be
2404     * migrated from legacy store files if the new store files aren't present on the device.
2405     *
2406     * This reads all the network configurations from:
2407     * 1. Shared WifiConfigStore.xml
2408     * 2. User WifiConfigStore.xml
2409     * 3. PerProviderSubscription.conf
2410     *
2411     * @return true on success, false otherwise.
2412     */
2413    public boolean loadFromStore() {
2414        if (!mWifiConfigStore.areStoresPresent()) {
2415            return migrateFromLegacyStore();
2416        }
2417
2418        WifiConfigStoreData storeData;
2419        long readStartTime = mClock.getElapsedSinceBootMillis();
2420        try {
2421            storeData = mWifiConfigStore.read();
2422        } catch (IOException e) {
2423            Log.wtf(TAG, "Reading from new store failed. All saved networks are lost!", e);
2424            return false;
2425        } catch (XmlPullParserException e) {
2426            Log.wtf(TAG, "XML deserialization of store failed. All saved networks are lost!", e);
2427            return false;
2428        }
2429        long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
2430        Log.d(TAG, "Reading from store completed in " + readTime + " ms.");
2431
2432        loadInternalData(storeData.getConfigurations(), storeData.getDeletedEphemeralSSIDs());
2433        return true;
2434    }
2435
2436    /**
2437     * Save the current snapshot of the in-memory lists to the config store.
2438     *
2439     * @param forceWrite Whether the write needs to be forced or not.
2440     * @return Whether the write was successful or not, this is applicable only for force writes.
2441     */
2442    public boolean saveToStore(boolean forceWrite) {
2443        ArrayList<WifiConfiguration> sharedConfigurations = new ArrayList<>();
2444        ArrayList<WifiConfiguration> userConfigurations = new ArrayList<>();
2445        for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
2446            // Don't persist ephemeral networks to store.
2447            if (!config.ephemeral) {
2448                // We push all shared networks & private networks not belonging to the current
2449                // user to the shared store. Ideally, private networks for other users should
2450                // not even be in memory,
2451                // But, this logic is in place to deal with store migration from N to O
2452                // because all networks were previously stored in a central file. We cannot
2453                // write these private networks to the user specific store until the corresponding
2454                // user logs in.
2455                if (config.shared || !WifiConfigurationUtil.doesUidBelongToAnyProfile(
2456                        config.creatorUid, mUserManager.getProfiles(mCurrentUserId))) {
2457                    sharedConfigurations.add(config);
2458                } else {
2459                    userConfigurations.add(config);
2460                }
2461            }
2462        }
2463        WifiConfigStoreData storeData =
2464                new WifiConfigStoreData(
2465                        sharedConfigurations, userConfigurations, mDeletedEphemeralSSIDs);
2466
2467        long writeStartTime = mClock.getElapsedSinceBootMillis();
2468        try {
2469            mWifiConfigStore.write(forceWrite, storeData);
2470        } catch (IOException e) {
2471            Log.wtf(TAG, "Writing to store failed. Saved networks maybe lost!", e);
2472            return false;
2473        } catch (XmlPullParserException e) {
2474            Log.wtf(TAG, "XML serialization for store failed. Saved networks maybe lost!", e);
2475            return false;
2476        }
2477        long writeTime = mClock.getElapsedSinceBootMillis() - writeStartTime;
2478        Log.d(TAG, "Writing to store completed in " + writeTime + " ms.");
2479        return true;
2480    }
2481
2482    /**
2483     * Helper method for logging into local log buffer.
2484     */
2485    private void localLog(String s) {
2486        if (mLocalLog != null) {
2487            mLocalLog.log(s);
2488        }
2489    }
2490
2491    /**
2492     * Dump the local log buffer and other internal state of WifiConfigManager.
2493     */
2494    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2495        pw.println("Dump of WifiConfigManager");
2496        pw.println("WifiConfigManager - Log Begin ----");
2497        mLocalLog.dump(fd, pw, args);
2498        pw.println("WifiConfigManager - Log End ----");
2499        pw.println("WifiConfigManager - Configured networks Begin ----");
2500        for (WifiConfiguration network: getInternalConfiguredNetworks()) {
2501            pw.println(network);
2502        }
2503        pw.println("WifiConfigManager - Configured networks End ----");
2504        pw.println("WifiConfigManager - Next network ID to be allocated " + mNextNetworkId);
2505        pw.println("WifiConfigManager - Last selected network ID " + mLastSelectedNetworkId);
2506    }
2507}
2508