Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.wifi;
     18 
     19 import android.content.pm.UserInfo;
     20 import android.net.IpConfiguration;
     21 import android.net.StaticIpConfiguration;
     22 import android.net.wifi.WifiConfiguration;
     23 import android.net.wifi.WifiEnterpriseConfig;
     24 import android.net.wifi.WifiScanner;
     25 import android.os.UserHandle;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 
     29 import com.android.internal.annotations.VisibleForTesting;
     30 import com.android.server.wifi.util.NativeUtil;
     31 
     32 import java.nio.charset.StandardCharsets;
     33 import java.security.cert.X509Certificate;
     34 import java.util.Arrays;
     35 import java.util.BitSet;
     36 import java.util.Comparator;
     37 import java.util.List;
     38 import java.util.Objects;
     39 
     40 /**
     41  * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations.
     42  * Currently contains:
     43  *   > Helper method to check if the WifiConfiguration object is visible to the provided users.
     44  *   > Helper methods to identify the encryption of a WifiConfiguration object.
     45  */
     46 public class WifiConfigurationUtil {
     47     private static final String TAG = "WifiConfigurationUtil";
     48 
     49     /**
     50      * Constants used for validating external config objects.
     51      */
     52     private static final int ENCLOSING_QUTOES_LEN = 2;
     53     private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUTOES_LEN;
     54     private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUTOES_LEN;
     55     private static final int SSID_HEX_MIN_LEN = 2;
     56     private static final int SSID_HEX_MAX_LEN = 64;
     57     private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUTOES_LEN;
     58     private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUTOES_LEN;
     59     private static final int PSK_HEX_LEN = 64;
     60     @VisibleForTesting
     61     public static final String PASSWORD_MASK = "*";
     62 
     63     /**
     64      * Check whether a network configuration is visible to a user or any of its managed profiles.
     65      *
     66      * @param config   the network configuration whose visibility should be checked
     67      * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
     68      *                 via {@link android.os.UserManager#getProfiles})
     69      * @return whether the network configuration is visible to the user or any of its managed
     70      * profiles
     71      */
     72     public static boolean isVisibleToAnyProfile(WifiConfiguration config, List<UserInfo> profiles) {
     73         return (config.shared || doesUidBelongToAnyProfile(config.creatorUid, profiles));
     74     }
     75 
     76     /**
     77      * Check whether a uid belong to a user or any of its managed profiles.
     78      *
     79      * @param uid      uid of the app.
     80      * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
     81      *                 via {@link android.os.UserManager#getProfiles})
     82      * @return whether the uid belongs to the user or any of its managed profiles.
     83      */
     84     public static boolean doesUidBelongToAnyProfile(int uid, List<UserInfo> profiles) {
     85         final int userId = UserHandle.getUserId(uid);
     86         for (UserInfo profile : profiles) {
     87             if (profile.id == userId) {
     88                 return true;
     89             }
     90         }
     91         return false;
     92     }
     93 
     94     /**
     95      * Checks if the provided |wepKeys| array contains any non-null value;
     96      */
     97     public static boolean hasAnyValidWepKey(String[] wepKeys) {
     98         for (int i = 0; i < wepKeys.length; i++) {
     99             if (wepKeys[i] != null) {
    100                 return true;
    101             }
    102         }
    103         return false;
    104     }
    105 
    106     /**
    107      * Helper method to check if the provided |config| corresponds to a PSK network or not.
    108      */
    109     public static boolean isConfigForPskNetwork(WifiConfiguration config) {
    110         return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK);
    111     }
    112 
    113     /**
    114      * Helper method to check if the provided |config| corresponds to a EAP network or not.
    115      */
    116     public static boolean isConfigForEapNetwork(WifiConfiguration config) {
    117         return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
    118                 || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
    119     }
    120 
    121     /**
    122      * Helper method to check if the provided |config| corresponds to a WEP network or not.
    123      */
    124     public static boolean isConfigForWepNetwork(WifiConfiguration config) {
    125         return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
    126                 && hasAnyValidWepKey(config.wepKeys));
    127     }
    128 
    129     /**
    130      * Helper method to check if the provided |config| corresponds to an open network or not.
    131      */
    132     public static boolean isConfigForOpenNetwork(WifiConfiguration config) {
    133         return !(isConfigForWepNetwork(config) || isConfigForPskNetwork(config)
    134                 || isConfigForEapNetwork(config));
    135     }
    136 
    137     /**
    138      * Compare existing and new WifiConfiguration objects after a network update and return if
    139      * IP parameters have changed or not.
    140      *
    141      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
    142      * @param newConfig      New WifiConfiguration object corresponding to the network.
    143      * @return true if IP parameters have changed, false otherwise.
    144      */
    145     public static boolean hasIpChanged(WifiConfiguration existingConfig,
    146             WifiConfiguration newConfig) {
    147         if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) {
    148             return true;
    149         }
    150         if (newConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
    151             return !Objects.equals(existingConfig.getStaticIpConfiguration(),
    152                     newConfig.getStaticIpConfiguration());
    153         }
    154         return false;
    155     }
    156 
    157     /**
    158      * Compare existing and new WifiConfiguration objects after a network update and return if
    159      * proxy parameters have changed or not.
    160      *
    161      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
    162      * @param newConfig      New WifiConfiguration object corresponding to the network.
    163      * @return true if proxy parameters have changed, false if no existing config and proxy settings
    164      * are NONE, false otherwise.
    165      */
    166     public static boolean hasProxyChanged(WifiConfiguration existingConfig,
    167             WifiConfiguration newConfig) {
    168         if (existingConfig == null) {
    169             return newConfig.getProxySettings() != IpConfiguration.ProxySettings.NONE;
    170         }
    171         if (newConfig.getProxySettings() != existingConfig.getProxySettings()) {
    172             return true;
    173         }
    174         return !Objects.equals(existingConfig.getHttpProxy(), newConfig.getHttpProxy());
    175     }
    176 
    177     /**
    178      * Compare existing and new WifiEnterpriseConfig objects after a network update and return if
    179      * credential parameters have changed or not.
    180      *
    181      * @param existingEnterpriseConfig Existing WifiConfiguration object corresponding to the
    182      *                                 network.
    183      * @param newEnterpriseConfig      New WifiConfiguration object corresponding to the network.
    184      * @return true if credentials have changed, false otherwise.
    185      */
    186     @VisibleForTesting
    187     public static boolean hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig,
    188             WifiEnterpriseConfig newEnterpriseConfig) {
    189         if (existingEnterpriseConfig != null && newEnterpriseConfig != null) {
    190             if (existingEnterpriseConfig.getEapMethod() != newEnterpriseConfig.getEapMethod()) {
    191                 return true;
    192             }
    193             if (existingEnterpriseConfig.getPhase2Method()
    194                     != newEnterpriseConfig.getPhase2Method()) {
    195                 return true;
    196             }
    197             if (!TextUtils.equals(existingEnterpriseConfig.getIdentity(),
    198                                   newEnterpriseConfig.getIdentity())
    199                     || !TextUtils.equals(existingEnterpriseConfig.getAnonymousIdentity(),
    200                                          newEnterpriseConfig.getAnonymousIdentity())) {
    201                 return true;
    202             }
    203             if (!TextUtils.equals(existingEnterpriseConfig.getPassword(),
    204                                     newEnterpriseConfig.getPassword())) {
    205                 return true;
    206             }
    207             X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates();
    208             X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates();
    209             if (!Arrays.equals(existingCaCerts, newCaCerts)) {
    210                 return true;
    211             }
    212         } else {
    213             // One of the configs may have an enterpriseConfig
    214             if (existingEnterpriseConfig != null || newEnterpriseConfig != null) {
    215                 return true;
    216             }
    217         }
    218         return false;
    219     }
    220 
    221     /**
    222      * Compare existing and new WifiConfiguration objects after a network update and return if
    223      * credential parameters have changed or not.
    224      *
    225      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
    226      * @param newConfig      New WifiConfiguration object corresponding to the network.
    227      * @return true if credentials have changed, false otherwise.
    228      */
    229     public static boolean hasCredentialChanged(WifiConfiguration existingConfig,
    230             WifiConfiguration newConfig) {
    231         if (!Objects.equals(existingConfig.allowedKeyManagement,
    232                 newConfig.allowedKeyManagement)) {
    233             return true;
    234         }
    235         if (!Objects.equals(existingConfig.allowedProtocols, newConfig.allowedProtocols)) {
    236             return true;
    237         }
    238         if (!Objects.equals(existingConfig.allowedAuthAlgorithms,
    239                 newConfig.allowedAuthAlgorithms)) {
    240             return true;
    241         }
    242         if (!Objects.equals(existingConfig.allowedPairwiseCiphers,
    243                 newConfig.allowedPairwiseCiphers)) {
    244             return true;
    245         }
    246         if (!Objects.equals(existingConfig.allowedGroupCiphers,
    247                 newConfig.allowedGroupCiphers)) {
    248             return true;
    249         }
    250         if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) {
    251             return true;
    252         }
    253         if (!Arrays.equals(existingConfig.wepKeys, newConfig.wepKeys)) {
    254             return true;
    255         }
    256         if (existingConfig.wepTxKeyIndex != newConfig.wepTxKeyIndex) {
    257             return true;
    258         }
    259         if (existingConfig.hiddenSSID != newConfig.hiddenSSID) {
    260             return true;
    261         }
    262         if (hasEnterpriseConfigChanged(existingConfig.enterpriseConfig,
    263                 newConfig.enterpriseConfig)) {
    264             return true;
    265         }
    266         return false;
    267     }
    268 
    269     private static boolean validateSsid(String ssid, boolean isAdd) {
    270         if (isAdd) {
    271             if (ssid == null) {
    272                 Log.e(TAG, "validateSsid : null string");
    273                 return false;
    274             }
    275         } else {
    276             if (ssid == null) {
    277                 // This is an update, so the SSID can be null if that is not being changed.
    278                 return true;
    279             }
    280         }
    281         if (ssid.isEmpty()) {
    282             Log.e(TAG, "validateSsid failed: empty string");
    283             return false;
    284         }
    285         if (ssid.startsWith("\"")) {
    286             // UTF-8 SSID string
    287             byte[] ssidBytes = ssid.getBytes(StandardCharsets.UTF_8);
    288             if (ssidBytes.length < SSID_UTF_8_MIN_LEN) {
    289                 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too small: "
    290                         + ssidBytes.length);
    291                 return false;
    292             }
    293             if (ssidBytes.length > SSID_UTF_8_MAX_LEN) {
    294                 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too large: "
    295                         + ssidBytes.length);
    296                 return false;
    297             }
    298         } else {
    299             // HEX SSID string
    300             if (ssid.length() < SSID_HEX_MIN_LEN) {
    301                 Log.e(TAG, "validateSsid failed: hex string size too small: " + ssid.length());
    302                 return false;
    303             }
    304             if (ssid.length() > SSID_HEX_MAX_LEN) {
    305                 Log.e(TAG, "validateSsid failed: hex string size too large: " + ssid.length());
    306                 return false;
    307             }
    308         }
    309         try {
    310             NativeUtil.decodeSsid(ssid);
    311         } catch (IllegalArgumentException e) {
    312             Log.e(TAG, "validateSsid failed: malformed string: " + ssid);
    313             return false;
    314         }
    315         return true;
    316     }
    317 
    318     private static boolean validatePsk(String psk, boolean isAdd) {
    319         if (isAdd) {
    320             if (psk == null) {
    321                 Log.e(TAG, "validatePsk: null string");
    322                 return false;
    323             }
    324         } else {
    325             if (psk == null) {
    326                 // This is an update, so the psk can be null if that is not being changed.
    327                 return true;
    328             } else if (psk.equals(PASSWORD_MASK)) {
    329                 // This is an update, so the app might have returned back the masked password, let
    330                 // it thru. WifiConfigManager will handle it.
    331                 return true;
    332             }
    333         }
    334         if (psk.isEmpty()) {
    335             Log.e(TAG, "validatePsk failed: empty string");
    336             return false;
    337         }
    338         if (psk.startsWith("\"")) {
    339             // ASCII PSK string
    340             byte[] pskBytes = psk.getBytes(StandardCharsets.US_ASCII);
    341             if (pskBytes.length < PSK_ASCII_MIN_LEN) {
    342                 Log.e(TAG, "validatePsk failed: ascii string size too small: " + pskBytes.length);
    343                 return false;
    344             }
    345             if (pskBytes.length > PSK_ASCII_MAX_LEN) {
    346                 Log.e(TAG, "validatePsk failed: ascii string size too large: " + pskBytes.length);
    347                 return false;
    348             }
    349         } else {
    350             // HEX PSK string
    351             if (psk.length() != PSK_HEX_LEN) {
    352                 Log.e(TAG, "validatePsk failed: hex string size mismatch: " + psk.length());
    353                 return false;
    354             }
    355         }
    356         try {
    357             NativeUtil.hexOrQuotedStringToBytes(psk);
    358         } catch (IllegalArgumentException e) {
    359             Log.e(TAG, "validatePsk failed: malformed string: " + psk);
    360             return false;
    361         }
    362         return true;
    363     }
    364 
    365     private static boolean validateBitSet(BitSet bitSet, int validValuesLength) {
    366         if (bitSet == null) return false;
    367         BitSet clonedBitset = (BitSet) bitSet.clone();
    368         clonedBitset.clear(0, validValuesLength);
    369         return clonedBitset.isEmpty();
    370     }
    371 
    372     private static boolean validateBitSets(WifiConfiguration config) {
    373         // 1. Check |allowedKeyManagement|.
    374         if (!validateBitSet(config.allowedKeyManagement,
    375                 WifiConfiguration.KeyMgmt.strings.length)) {
    376             Log.e(TAG, "validateBitsets failed: invalid allowedKeyManagement bitset "
    377                     + config.allowedKeyManagement);
    378             return false;
    379         }
    380         // 2. Check |allowedProtocols|.
    381         if (!validateBitSet(config.allowedProtocols,
    382                 WifiConfiguration.Protocol.strings.length)) {
    383             Log.e(TAG, "validateBitsets failed: invalid allowedProtocols bitset "
    384                     + config.allowedProtocols);
    385             return false;
    386         }
    387         // 3. Check |allowedAuthAlgorithms|.
    388         if (!validateBitSet(config.allowedAuthAlgorithms,
    389                 WifiConfiguration.AuthAlgorithm.strings.length)) {
    390             Log.e(TAG, "validateBitsets failed: invalid allowedAuthAlgorithms bitset "
    391                     + config.allowedAuthAlgorithms);
    392             return false;
    393         }
    394         // 4. Check |allowedGroupCiphers|.
    395         if (!validateBitSet(config.allowedGroupCiphers,
    396                 WifiConfiguration.GroupCipher.strings.length)) {
    397             Log.e(TAG, "validateBitsets failed: invalid allowedGroupCiphers bitset "
    398                     + config.allowedGroupCiphers);
    399             return false;
    400         }
    401         // 5. Check |allowedPairwiseCiphers|.
    402         if (!validateBitSet(config.allowedPairwiseCiphers,
    403                 WifiConfiguration.PairwiseCipher.strings.length)) {
    404             Log.e(TAG, "validateBitsets failed: invalid allowedPairwiseCiphers bitset "
    405                     + config.allowedPairwiseCiphers);
    406             return false;
    407         }
    408         return true;
    409     }
    410 
    411     private static boolean validateKeyMgmt(BitSet keyMgmnt) {
    412         if (keyMgmnt.cardinality() > 1) {
    413             if (keyMgmnt.cardinality() != 2) {
    414                 Log.e(TAG, "validateKeyMgmt failed: cardinality != 2");
    415                 return false;
    416             }
    417             if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
    418                 Log.e(TAG, "validateKeyMgmt failed: not WPA_EAP");
    419                 return false;
    420             }
    421             if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X)
    422                     && !keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
    423                 Log.e(TAG, "validateKeyMgmt failed: not PSK or 8021X");
    424                 return false;
    425             }
    426         }
    427         return true;
    428     }
    429 
    430     private static boolean validateIpConfiguration(IpConfiguration ipConfig) {
    431         if (ipConfig == null) {
    432             Log.e(TAG, "validateIpConfiguration failed: null IpConfiguration");
    433             return false;
    434         }
    435         if (ipConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
    436             StaticIpConfiguration staticIpConfig = ipConfig.getStaticIpConfiguration();
    437             if (staticIpConfig == null) {
    438                 Log.e(TAG, "validateIpConfiguration failed: null StaticIpConfiguration");
    439                 return false;
    440             }
    441             if (staticIpConfig.ipAddress == null) {
    442                 Log.e(TAG, "validateIpConfiguration failed: null static ip Address");
    443                 return false;
    444             }
    445         }
    446         return true;
    447     }
    448 
    449     /**
    450      * Enums to specify if the provided config is being validated for add or update.
    451      */
    452     public static final boolean VALIDATE_FOR_ADD = true;
    453     public static final boolean VALIDATE_FOR_UPDATE = false;
    454 
    455     /**
    456      * Validate the configuration received from an external application.
    457      *
    458      * This method checks for the following parameters:
    459      * 1. {@link WifiConfiguration#SSID}
    460      * 2. {@link WifiConfiguration#preSharedKey}
    461      * 3. {@link WifiConfiguration#allowedKeyManagement}
    462      * 4. {@link WifiConfiguration#allowedProtocols}
    463      * 5. {@link WifiConfiguration#allowedAuthAlgorithms}
    464      * 6. {@link WifiConfiguration#allowedGroupCiphers}
    465      * 7. {@link WifiConfiguration#allowedPairwiseCiphers}
    466      * 8. {@link WifiConfiguration#getIpConfiguration()}
    467      *
    468      * @param config {@link WifiConfiguration} received from an external application.
    469      * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add,
    470      *              {@link #VALIDATE_FOR_UPDATE} for a network config received for an update.
    471      *              These 2 cases need to be handled differently because the config received for an
    472      *              update could contain only the fields that are being changed.
    473      * @return true if the parameters are valid, false otherwise.
    474      */
    475     public static boolean validate(WifiConfiguration config, boolean isAdd) {
    476         if (!validateSsid(config.SSID, isAdd)) {
    477             return false;
    478         }
    479         if (!validateBitSets(config)) {
    480             return false;
    481         }
    482         if (!validateKeyMgmt(config.allowedKeyManagement)) {
    483             return false;
    484         }
    485         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
    486                 && !validatePsk(config.preSharedKey, isAdd)) {
    487             return false;
    488         }
    489         if (!validateIpConfiguration(config.getIpConfiguration())) {
    490             return false;
    491         }
    492         // TBD: Validate some enterprise params as well in the future here.
    493         return true;
    494     }
    495 
    496     /**
    497      * Check if the provided two networks are the same.
    498      * Note: This does not check if network selection BSSID's are the same.
    499      *
    500      * @param config  Configuration corresponding to a network.
    501      * @param config1 Configuration corresponding to another network.
    502      *
    503      * @return true if |config| and |config1| are the same network.
    504      *         false otherwise.
    505      */
    506     public static boolean isSameNetwork(WifiConfiguration config, WifiConfiguration config1) {
    507         if (config == null && config1 == null) {
    508             return true;
    509         }
    510         if (config == null || config1 == null) {
    511             return false;
    512         }
    513         if (config.networkId != config1.networkId) {
    514             return false;
    515         }
    516         if (!Objects.equals(config.SSID, config1.SSID)) {
    517             return false;
    518         }
    519         if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
    520             return false;
    521         }
    522         return true;
    523     }
    524 
    525     /**
    526      * Create a PnoNetwork object from the provided WifiConfiguration.
    527      *
    528      * @param config      Configuration corresponding to the network.
    529      * @return PnoNetwork object corresponding to the network.
    530      */
    531     public static WifiScanner.PnoSettings.PnoNetwork createPnoNetwork(
    532             WifiConfiguration config) {
    533         WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
    534                 new WifiScanner.PnoSettings.PnoNetwork(config.SSID);
    535         if (config.hiddenSSID) {
    536             pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
    537         }
    538         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
    539         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
    540         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
    541             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
    542         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
    543                 || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
    544             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
    545         } else {
    546             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
    547         }
    548         return pnoNetwork;
    549     }
    550 
    551 
    552     /**
    553      * General WifiConfiguration list sorting algorithm:
    554      * 1, Place the fully enabled networks first.
    555      * 2. Next place all the temporarily disabled networks.
    556      * 3. Place the permanently disabled networks last (Permanently disabled networks are removed
    557      * before WifiConfigManager uses this comparator today!).
    558      *
    559      * Among the networks with the same status, sort them in the order determined by the return of
    560      * {@link #compareNetworksWithSameStatus(WifiConfiguration, WifiConfiguration)} method
    561      * implementation.
    562      */
    563     public abstract static class WifiConfigurationComparator implements
    564             Comparator<WifiConfiguration> {
    565         private static final int ENABLED_NETWORK_SCORE = 3;
    566         private static final int TEMPORARY_DISABLED_NETWORK_SCORE = 2;
    567         private static final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1;
    568 
    569         @Override
    570         public int compare(WifiConfiguration a, WifiConfiguration b) {
    571             int configAScore = getNetworkStatusScore(a);
    572             int configBScore = getNetworkStatusScore(b);
    573             if (configAScore == configBScore) {
    574                 return compareNetworksWithSameStatus(a, b);
    575             } else {
    576                 return Integer.compare(configBScore, configAScore);
    577             }
    578         }
    579 
    580         // This needs to be implemented by the connected/disconnected PNO list comparator.
    581         abstract int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b);
    582 
    583         /**
    584          * Returns an integer representing a score for each configuration. The scores are assigned
    585          * based on the status of the configuration. The scores are assigned according to the order:
    586          * Fully enabled network > Temporarily disabled network > Permanently disabled network.
    587          */
    588         private int getNetworkStatusScore(WifiConfiguration config) {
    589             if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
    590                 return ENABLED_NETWORK_SCORE;
    591             } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
    592                 return TEMPORARY_DISABLED_NETWORK_SCORE;
    593             } else {
    594                 return PERMANENTLY_DISABLED_NETWORK_SCORE;
    595             }
    596         }
    597     }
    598 }
    599