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