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