Wifi: add throughputPredictor and throughputScorer classes

They are used in WifiNetworkSelector for predict throughput and
calculate network selection score with Nss, Rssi, channelWidth,
wifiStandard and channelUtilization info collected from scan and
bluetoothConnected information from BluetoothAdaptor broadcast message.

Bug: 141770991
Test: Wifi unit test with frameworks/opt/net/wifi/tests/wifitests/runtests.sh
Test: run manual test to make sure throughput and score are calculated
      correctly with various APs with 2, 4 and 8 antennas in 20, 40 and 80MHz
      modes with and without BT connected.

Change-Id: I1ae36460d2f719cce157d94f5b65039597878f49
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 68dfe08..8688651 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -3103,6 +3103,7 @@
                 case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
                     mBluetoothConnectionActive =
                             (message.arg1 != BluetoothAdapter.STATE_DISCONNECTED);
+                    mWifiConnectivityManager.setBluetoothConnected(mBluetoothConnectionActive);
                     break;
                 case CMD_ENABLE_RSSI_POLL:
                     mEnableRssiPolling = (message.arg1 == 1);
@@ -3947,6 +3948,7 @@
                             != BluetoothAdapter.STATE_DISCONNECTED);
                     mWifiNative.setBluetoothCoexistenceScanMode(
                             mInterfaceName, mBluetoothConnectionActive);
+                    mWifiConnectivityManager.setBluetoothConnected(mBluetoothConnectionActive);
                     break;
                 case CMD_SET_SUSPEND_OPT_ENABLED:
                     if (message.arg1 == 1) {
diff --git a/service/java/com/android/server/wifi/ThroughputPredictor.java b/service/java/com/android/server/wifi/ThroughputPredictor.java
new file mode 100644
index 0000000..a8e8ada
--- /dev/null
+++ b/service/java/com/android/server/wifi/ThroughputPredictor.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MAX_CHANNEL_UTILIZATION;
+import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MIN_CHANNEL_UTILIZATION;
+
+import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.util.Log;
+
+import com.android.wifi.R;
+
+/**
+ * A class that predicts network throughput based on RSSI, channel utilization, channel width,
+ * WiFi standard (PHY/MAC mode), Nss and other radio information.
+ */
+public class ThroughputPredictor {
+    private static final String TAG = "WifiThroughputPredictor";
+    private static final boolean DBG = false;
+
+    // Default value of channel utilization at 2G when channel utilization is not available from
+    // BssLoad IE or from link layer stats
+    public static final int CHANNEL_UTILIZATION_DEFAULT_2G = MAX_CHANNEL_UTILIZATION * 6 / 16;
+    // Default value of channel utilization at 5G when channel utilization is not available from
+    // BssLoad IE or from link layer stats
+    public static final int CHANNEL_UTILIZATION_DEFAULT_5G = MAX_CHANNEL_UTILIZATION / 16;
+    // Channel utilization boost when bluetooth is in the connected mode
+    public static final int CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G = MAX_CHANNEL_UTILIZATION / 4;
+
+    // Number of data tones per OFDM symbol
+    private static final int NUM_TONE_PER_SYM_LEGACY = 48;
+    private static final int NUM_TONE_PER_SYM_11N_20MHZ = 52;
+    private static final int NUM_TONE_PER_SYM_11N_40MHZ = 108;
+    private static final int NUM_TONE_PER_SYM_11AC_20MHZ = 52;
+    private static final int NUM_TONE_PER_SYM_11AC_40MHZ = 108;
+    private static final int NUM_TONE_PER_SYM_11AC_80MHZ = 234;
+    private static final int NUM_TONE_PER_SYM_11AC_160MHZ = 468;
+    private static final int NUM_TONE_PER_SYM_11AX_20MHZ = 234;
+    private static final int NUM_TONE_PER_SYM_11AX_40MHZ = 468;
+    private static final int NUM_TONE_PER_SYM_11AX_80MHZ = 980;
+    private static final int NUM_TONE_PER_SYM_11AX_160MHZ = 1960;
+
+    // 11ag OFDM symbol duration in ns
+    private static final int SYM_DURATION_LEGACY_NS = 4000;
+    // 11n OFDM symbol duration in ns with 0.4us guard interval
+    private static final int SYM_DURATION_11N_NS = 3600;
+    // 11ac OFDM symbol duration in ns with 0.4us guard interval
+    private static final int SYM_DURATION_11AC_NS = 3600;
+    // 11n OFDM symbol duration in ns with 0.8us guard interval
+    private static final int SYM_DURATION_11AX_NS = 13600;
+    private static final int MICRO_TO_NANO_RATIO = 1000;
+
+    // The scaling factor for integer representation of bitPerTone and MAX_BITS_PER_TONE_XXX
+    private static final int BIT_PER_TONE_SCALE = 1000;
+    private static final int MAX_BITS_PER_TONE_LEGACY = (int) (6 * 3.0 * BIT_PER_TONE_SCALE / 4.0);
+    private static final int MAX_BITS_PER_TONE_11N = (int) (6 * 5.0 * BIT_PER_TONE_SCALE / 6.0);
+    private static final int MAX_BITS_PER_TONE_11AC = (int) (8 * 5.0 * BIT_PER_TONE_SCALE / 6.0);
+    private static final int MAX_BITS_PER_TONE_11AX = (int) (10 * 5.0 * BIT_PER_TONE_SCALE / 6.0);
+
+    // snrDb-to-bitPerTone lookup table (LUT) used at low SNR
+    // snr = Math.pow(10.0, snrDb / 10.0);
+    // bitPerTone = (int) (Math.log10(1 + snr) / Math.log10(2.0) * BIT_PER_TONE_SCALE)
+    private static final int TWO_IN_DB = 3;
+    private static final int SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE = BIT_PER_TONE_SCALE / TWO_IN_DB;
+    private static final int SNR_DB_TO_BIT_PER_TONE_LUT_MIN = -10; // minimum snrDb supported by LUT
+    private static final int SNR_DB_TO_BIT_PER_TONE_LUT_MAX = 9; // maximum snrDb supported by LUT
+    private static final int[] SNR_DB_TO_BIT_PER_TONE_LUT = {0, 171, 212, 262, 323, 396, 484, 586,
+            706, 844, 1000, 1176, 1370, 1583, 1812, 2058, 2317, 2588, 2870, 3161};
+    // Thermal noise floor power in dBm integrated over 20MHz with 5.5dB noise figure at 25C
+    private static final int NOISE_FLOOR_20MHZ_DBM = -96;
+    // A fudge factor to represent HW implementation margin in dB.
+    // Predicted throughput matches pretty well with OTA throughput with this fudge factor.
+    private static final int SNR_MARGIN_DB = 16;
+    private static final int MAX_NUM_SPATIAL_STREAM_11AX = 8;
+    private static final int MAX_NUM_SPATIAL_STREAM_11AC = 8;
+    private static final int MAX_NUM_SPATIAL_STREAM_11N = 4;
+    private static final int MAX_NUM_SPATIAL_STREAM_LEGACY = 1;
+
+    private final boolean mAxSupported;
+    private final boolean mContiguous160MHzSupported;
+    private final int mMaxNumSpatialStreamSupported;
+
+    ThroughputPredictor(Context context) {
+        // TODO: b/144576344 get the following information from HAL
+        mAxSupported = context.getResources().getBoolean(
+                R.bool.config_wifi_11ax_supported);
+        mContiguous160MHzSupported = context.getResources().getBoolean(
+                R.bool.config_wifi_contiguous_160mhz_supported);
+        mMaxNumSpatialStreamSupported = context.getResources().getInteger(
+                R.integer.config_wifi_max_num_spatial_stream_supported);
+    }
+
+    /**
+     * Predict network throughput
+     * @param wifiStandard the highest wifi standard supported by AP
+     * @param channelWidthAp the channel bandwidth of AP
+     * @param rssiDbm the scan RSSI in dBm
+     * @param frequency the center frequency of primary 20MHz channel
+     * @param maxNumSpatialStreamAp the maximum number of spatial streams supported by AP
+     * @param channelUtilizationBssLoad the channel utilization ratio indicated from BssLoad IE
+     * @param channelUtilizationLinkLayerStats the channel utilization ratio detected from scan
+     * @param isBluetoothConnected whether the bluetooth adaptor is in connected mode
+     * @return predicted throughput in Mbps
+     */
+    public int predictThroughput(@ScanResult.WifiStandard int wifiStandard,
+            int channelWidthAp, int rssiDbm, int frequency, int maxNumSpatialStreamAp,
+            int channelUtilizationBssLoad, int channelUtilizationLinkLayerStats,
+            boolean isBluetoothConnected) {
+
+        int maxNumSpatialStream = Math.min(mMaxNumSpatialStreamSupported, maxNumSpatialStreamAp);
+
+        // Downgrade to AC mode if 11AX AP is found but 11AX mode is not supported by the device
+        if (!mAxSupported && wifiStandard == ScanResult.WIFI_STANDARD_11AX) {
+            wifiStandard = ScanResult.WIFI_STANDARD_11AC;
+        }
+
+        int channelWidth = channelWidthAp;
+        // Downgrade to 80MHz if 160MHz AP is found but 160MHz mode is not supported by the device
+        if (!mContiguous160MHzSupported && (channelWidth == ScanResult.CHANNEL_WIDTH_160MHZ)) {
+            channelWidth = ScanResult.CHANNEL_WIDTH_80MHZ;
+        }
+
+        // channel bandwidth in MHz = 20MHz * (2 ^ channelWidthFactor);
+        int channelWidthFactor;
+        int numTonePerSym;
+        int symDurationNs;
+        int maxBitsPerTone;
+        if (wifiStandard == ScanResult.WIFI_STANDARD_LEGACY) {
+            numTonePerSym = NUM_TONE_PER_SYM_LEGACY;
+            channelWidthFactor = 0;
+            maxNumSpatialStream = MAX_NUM_SPATIAL_STREAM_LEGACY;
+            maxBitsPerTone = MAX_BITS_PER_TONE_LEGACY;
+            symDurationNs = SYM_DURATION_LEGACY_NS;
+        } else if (wifiStandard == ScanResult.WIFI_STANDARD_11N) {
+            if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
+                numTonePerSym = NUM_TONE_PER_SYM_11N_20MHZ;
+                channelWidthFactor = 0;
+            } else {
+                numTonePerSym = NUM_TONE_PER_SYM_11N_40MHZ;
+                channelWidthFactor = 1;
+            }
+            maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11N);
+            maxBitsPerTone = MAX_BITS_PER_TONE_11N;
+            symDurationNs = SYM_DURATION_11N_NS;
+        } else if (wifiStandard == ScanResult.WIFI_STANDARD_11AC) {
+            if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
+                numTonePerSym = NUM_TONE_PER_SYM_11AC_20MHZ;
+                channelWidthFactor = 0;
+            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_40MHZ) {
+                numTonePerSym = NUM_TONE_PER_SYM_11AC_40MHZ;
+                channelWidthFactor = 1;
+            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_80MHZ) {
+                numTonePerSym = NUM_TONE_PER_SYM_11AC_80MHZ;
+                channelWidthFactor = 2;
+            } else {
+                numTonePerSym = NUM_TONE_PER_SYM_11AC_160MHZ;
+                channelWidthFactor = 3;
+            }
+            maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11AC);
+            maxBitsPerTone = MAX_BITS_PER_TONE_11AC;
+            symDurationNs = SYM_DURATION_11AC_NS;
+        } else { // ScanResult.WIFI_STANDARD_11AX
+            if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
+                numTonePerSym = NUM_TONE_PER_SYM_11AX_20MHZ;
+                channelWidthFactor = 0;
+            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_40MHZ) {
+                numTonePerSym = NUM_TONE_PER_SYM_11AX_40MHZ;
+                channelWidthFactor = 1;
+            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_80MHZ) {
+                numTonePerSym = NUM_TONE_PER_SYM_11AX_80MHZ;
+                channelWidthFactor = 2;
+            } else {
+                numTonePerSym = NUM_TONE_PER_SYM_11AX_160MHZ;
+                channelWidthFactor = 3;
+            }
+            maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11AX);
+            maxBitsPerTone = MAX_BITS_PER_TONE_11AX;
+            symDurationNs = SYM_DURATION_11AX_NS;
+        }
+        // noiseFloorDbBoost = 10 * log10 * (2 ^ channelWidthFactor)
+        int noiseFloorDbBoost = TWO_IN_DB * channelWidthFactor;
+        int noiseFloorDbm = NOISE_FLOOR_20MHZ_DBM + noiseFloorDbBoost + SNR_MARGIN_DB;
+        int snrDb  = rssiDbm - noiseFloorDbm;
+
+        int bitPerTone = calculateBitPerTone(snrDb);
+        bitPerTone = Math.min(bitPerTone, maxBitsPerTone);
+
+        long bitPerToneTotal = bitPerTone * maxNumSpatialStream;
+        long numBitPerSym = bitPerToneTotal * numTonePerSym;
+        int phyRateMbps =  (int) ((numBitPerSym * MICRO_TO_NANO_RATIO)
+                / (symDurationNs * BIT_PER_TONE_SCALE));
+
+        int channelUtilization = getValidChannelUtilization(frequency,
+                channelUtilizationBssLoad,
+                channelUtilizationLinkLayerStats,
+                isBluetoothConnected);
+
+        int airTimeFraction = calculateAirTimeFraction(channelUtilization, channelWidthFactor);
+
+        int throughputMbps = (phyRateMbps * airTimeFraction) / MAX_CHANNEL_UTILIZATION;
+
+        if (DBG) {
+            Log.d(TAG, " BW: " + channelWidthAp + " RSSI: "
+                    + rssiDbm + " Nss: " + maxNumSpatialStreamAp + " freq: " + frequency
+                    + " Mode: " + wifiStandard + " symDur: " + symDurationNs
+                    + " snrDb " + snrDb + " bitPerTone: " + bitPerTone
+                    + " rate: " + phyRateMbps + " throughput: " + throughputMbps);
+        }
+        return throughputMbps;
+    }
+
+    // Calculate the number of bits per tone based on the input of SNR in dB
+    // The output is scaled up by BIT_PER_TONE_SCALE for integer representation
+    private static int calculateBitPerTone(int snrDb) {
+        int bitPerTone;
+        if (snrDb <= SNR_DB_TO_BIT_PER_TONE_LUT_MAX) {
+            int lut_in_idx = Math.max(snrDb, SNR_DB_TO_BIT_PER_TONE_LUT_MIN)
+                    - SNR_DB_TO_BIT_PER_TONE_LUT_MIN;
+            lut_in_idx = Math.min(lut_in_idx, SNR_DB_TO_BIT_PER_TONE_LUT.length - 1);
+            bitPerTone = SNR_DB_TO_BIT_PER_TONE_LUT[lut_in_idx];
+        } else {
+            // bitPerTone = Math.log10(1+snr)/Math.log10(2) can be approximated as
+            // Math.log10(snr) / 0.3 = log10(10^(snrDb/10)) / 0.3 = snrDb / 3
+            // SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE = BIT_PER_TONE_SCALE / 3
+            bitPerTone = snrDb * SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE;
+        }
+        return bitPerTone;
+    }
+
+    private static int getValidChannelUtilization(int frequency, int channelUtilizationBssLoad,
+            int channelUtilizationLinkLayerStats, boolean isBluetoothConnected) {
+        int channelUtilization;
+        boolean is2G = (frequency < ScoringParams.MINIMUM_5GHZ_BAND_FREQUENCY_IN_MEGAHERTZ);
+        if (isValidUtilizationRatio(channelUtilizationBssLoad)) {
+            channelUtilization = channelUtilizationBssLoad;
+        } else if (isValidUtilizationRatio(channelUtilizationLinkLayerStats)) {
+            channelUtilization = channelUtilizationLinkLayerStats;
+        } else {
+            channelUtilization = is2G ? CHANNEL_UTILIZATION_DEFAULT_2G :
+                    CHANNEL_UTILIZATION_DEFAULT_5G;
+        }
+
+        if (is2G && isBluetoothConnected) {
+            channelUtilization += CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G;
+            channelUtilization = Math.min(channelUtilization, MAX_CHANNEL_UTILIZATION);
+        }
+        if (DBG) {
+            Log.d(TAG, " utilization (BssLoad) " + channelUtilizationBssLoad
+                    + " utilization (LLStats) " + channelUtilizationLinkLayerStats
+                    + " isBluetoothConnected: " + isBluetoothConnected
+                    + " final utilization: " + channelUtilization);
+        }
+        return channelUtilization;
+    }
+
+    /**
+     * Check if the channel utilization ratio is valid
+     */
+    private static boolean isValidUtilizationRatio(int utilizationRatio) {
+        return (utilizationRatio <= MAX_CHANNEL_UTILIZATION
+                && utilizationRatio >= MIN_CHANNEL_UTILIZATION);
+    }
+
+    // Calculate the available airtime fraction value which is multiplied by
+    // MAX_CHANNEL_UTILIZATION for integer representation. It is calculated as
+    // (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) * MAX_CHANNEL_UTILIZATION
+    private static int calculateAirTimeFraction(int channelUtilization, int channelWidthFactor) {
+        int airTimeFraction20MHz = MAX_CHANNEL_UTILIZATION - channelUtilization;
+        int airTimeFraction = airTimeFraction20MHz;
+        // For the cases of 40MHz or above, need to take
+        // (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) ^ (2 ^ channelWidthFactor)
+        // because channelUtilization is defined for primary 20MHz channel
+        for (int i = 1; i <= channelWidthFactor; ++i) {
+            airTimeFraction *= airTimeFraction;
+            airTimeFraction /= MAX_CHANNEL_UTILIZATION;
+        }
+        if (DBG) {
+            Log.d(TAG, " airTime20: " + airTimeFraction20MHz + " airTime: " + airTimeFraction);
+        }
+        return airTimeFraction;
+    }
+}
diff --git a/service/java/com/android/server/wifi/ThroughputScorer.java b/service/java/com/android/server/wifi/ThroughputScorer.java
new file mode 100644
index 0000000..1e42657
--- /dev/null
+++ b/service/java/com/android/server/wifi/ThroughputScorer.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.server.wifi.WifiCandidates.Candidate;
+import com.android.server.wifi.WifiCandidates.ScoredCandidate;
+
+import java.util.Collection;
+
+/**
+ * A candidate scorer that combines RSSI base score and network throughput score.
+ */
+final class ThroughputScorer implements WifiCandidates.CandidateScorer {
+    private static final String TAG = "ThroughputScorer";
+    private static final boolean DBG = false;
+    /**
+     * This should match WifiNetworkSelector.experimentIdFromIdentifier(getIdentifier())
+     * when using the default ScoringParams.
+     */
+    public static final int THROUGHPUT_SCORER_DEFAULT_EXPID = 42330058;
+
+    private final ScoringParams mScoringParams;
+
+    // config_wifi_framework_RSSI_SCORE_OFFSET
+    public static final int RSSI_SCORE_OFFSET = 85;
+
+    // config_wifi_framework_RSSI_SCORE_SLOPE
+    public static final int RSSI_SCORE_SLOPE_IS_4 = 4;
+
+    // config_wifi_framework_SECURITY_AWARD
+    public static final int SECURITY_AWARD = 10;
+
+    // config_wifi_framework_LAST_SELECTION_AWARD
+    public static final int LAST_SELECTION_AWARD_IS_480 = 480;
+
+    // Bonus score for current network
+    // High RSSI case:
+    //   Bonus RSSI score: 10 (equivalent to RSSI variation 2.5dB)
+    //   Bonus throughput score: 10 (equivalent to ~ 40Mbps).
+    // Low RSSI case:
+    //   Bonus RSSI score: 16 (equivalent to RSSI variation 4dB)
+    //   Bonus throughput score: 4 (equivalent to ~ 16Mbps).
+    public static final int CURRENT_NETWORK_BOOST = 20;
+
+    // Max throughput in 11AC 40MHz 2SS mode with zero channel utilization
+    public static final int MAX_THROUGHPUT_AC_40_MHZ_2SS_MBPS = 433;
+    // Max throughput score in 11AC 40MHz 2SS mode
+    public static final int MAX_THROUGHPUT_BONUS_SCORE_AC_40_MHZ_2SS = 120;
+
+    // Max throughput bonus score for all possible modes
+    public static final int MAX_THROUGHPUT_BONUS_SCORE = 200;
+
+    private static final boolean USE_USER_CONNECT_CHOICE = true;
+
+    ThroughputScorer(ScoringParams scoringParams) {
+        mScoringParams = scoringParams;
+    }
+
+    @Override
+    public String getIdentifier() {
+        return "ThroughputScorer";
+    }
+
+    /**
+     * Calculates an individual candidate's score.
+     */
+    private ScoredCandidate scoreCandidate(Candidate candidate) {
+        int rssiSaturationThreshold = mScoringParams.getGoodRssi(candidate.getFrequency());
+        int rssi = Math.min(candidate.getScanRssi(), rssiSaturationThreshold);
+        int rssiBaseScore = (rssi + RSSI_SCORE_OFFSET) * RSSI_SCORE_SLOPE_IS_4;
+
+        int throughputBonusScore = calculateThroughputBonusScore(candidate);
+
+        int lastSelectionBonusScore = (int)
+                (candidate.getLastSelectionWeight() * LAST_SELECTION_AWARD_IS_480);
+
+        int currentNetworkBoost = candidate.isCurrentNetwork() ? CURRENT_NETWORK_BOOST : 0;
+
+        int securityAward = candidate.isOpenNetwork() ? 0 : SECURITY_AWARD;
+
+        // To simulate the old strict priority rule, subtract a penalty based on
+        // which evaluator added the candidate.
+        int evaluatorGroupScore = -1000 * candidate.getEvaluatorId();
+
+        int score = rssiBaseScore + throughputBonusScore + lastSelectionBonusScore
+                + currentNetworkBoost + securityAward + evaluatorGroupScore;
+
+        if (DBG) {
+            Log.d(TAG, " rssiScore: " + rssiBaseScore
+                    + " throughputScore: " + throughputBonusScore
+                    + " lastSelectionBonus: " + lastSelectionBonusScore
+                    + " currentNetworkBoost: " + currentNetworkBoost
+                    + " securityAward: " + securityAward
+                    + " evaluatorScore: " + evaluatorGroupScore
+                    + " final score: " + score);
+        }
+
+        // The old method breaks ties on the basis of RSSI, which we can
+        // emulate easily since our score does not need to be an integer.
+        double tieBreaker = candidate.getScanRssi() / 1000.0;
+        return new ScoredCandidate(score + tieBreaker, 10,
+                USE_USER_CONNECT_CHOICE, candidate);
+    }
+
+    private int calculateThroughputBonusScore(Candidate candidate) {
+        int throughputScoreRaw = candidate.getPredictedThroughputMbps()
+                * MAX_THROUGHPUT_BONUS_SCORE_AC_40_MHZ_2SS
+                / MAX_THROUGHPUT_AC_40_MHZ_2SS_MBPS;
+        return Math.min(throughputScoreRaw, MAX_THROUGHPUT_BONUS_SCORE);
+    }
+
+    @Override
+    public ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates) {
+        ScoredCandidate choice = ScoredCandidate.NONE;
+        for (Candidate candidate : candidates) {
+            ScoredCandidate scoredCandidate = scoreCandidate(candidate);
+            if (scoredCandidate.value > choice.value) {
+                choice = scoredCandidate;
+            }
+        }
+        // Here we just return the highest scored candidate; we could
+        // compute a new score, if desired.
+        return choice;
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/WifiCandidates.java b/service/java/com/android/server/wifi/WifiCandidates.java
index 00e67f4..d0f3542 100644
--- a/service/java/com/android/server/wifi/WifiCandidates.java
+++ b/service/java/com/android/server/wifi/WifiCandidates.java
@@ -117,6 +117,10 @@
          */
         int getFrequency();
         /**
+         * Gets the predicted throughput in Mbps
+         */
+        int getPredictedThroughputMbps();
+        /**
          * Gets statistics from the scorecard.
          */
         @Nullable WifiScoreCardProto.Signal getEventStatistics(WifiScoreCardProto.Event event);
@@ -138,6 +142,7 @@
         private final boolean mIsCurrentNetwork;
         private final boolean mIsCurrentBssid;
         private final boolean mIsMetered;
+        private final int mPredictedThroughputMbps;
 
         CandidateImpl(Key key,
                 ScanDetail scanDetail,
@@ -148,7 +153,8 @@
                 double lastSelectionWeight,
                 boolean isCurrentNetwork,
                 boolean isCurrentBssid,
-                boolean isMetered) {
+                boolean isMetered,
+                int predictedThroughputMbps) {
             this.key = key;
             this.scanDetail = scanDetail;
             this.config = config;
@@ -159,6 +165,7 @@
             this.mIsCurrentNetwork = isCurrentNetwork;
             this.mIsCurrentBssid = isCurrentBssid;
             this.mIsMetered = isMetered;
+            this.mPredictedThroughputMbps = predictedThroughputMbps;
         }
 
         @Override
@@ -237,6 +244,11 @@
             return scanDetail.getScanResult().frequency;
         }
 
+        @Override
+        public int getPredictedThroughputMbps() {
+            return mPredictedThroughputMbps;
+        }
+
         /**
          * Accesses statistical information from the score card
          */
@@ -359,7 +371,8 @@
                     @WifiNetworkSelector.NetworkEvaluator.EvaluatorId int evaluatorId,
                     int evaluatorScore,
                     double lastSelectionWeightBetweenZeroAndOne,
-                    boolean isMetered) {
+                    boolean isMetered,
+                    int predictedThroughputMbps) {
         if (config == null) return failure();
         if (scanDetail == null) return failure();
         ScanResult scanResult = scanDetail.getScanResult();
@@ -393,7 +406,8 @@
                 Math.min(Math.max(lastSelectionWeightBetweenZeroAndOne, 0.0), 1.0),
                 config.networkId == mCurrentNetworkId,
                 bssid.equals(mCurrentBssid),
-                isMetered);
+                isMetered,
+                predictedThroughputMbps);
         mCandidates.put(key, candidate);
         return true;
     }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 4130138..f74ec3f 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -274,6 +274,13 @@
         }
     }
 
+    /**
+     * Set whether bluetooth is in the connected state
+     */
+    public void setBluetoothConnected(boolean isBlueToothConnected) {
+        mNetworkSelector.setBluetoothConnected(isBlueToothConnected);
+    }
+
     // All single scan results listener.
     //
     // Note: This is the listener for all the available single scan results,
@@ -613,6 +620,7 @@
         mConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener());
         mBssidBlocklistMonitor = mWifiInjector.getBssidBlocklistMonitor();
         mWifiChannelUtilization = mWifiInjector.getWifiChannelUtilization();
+        mNetworkSelector.setWifiChannelUtilization(mWifiChannelUtilization);
     }
 
     /** Returns maximum PNO score, before any awards/bonuses. */
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 0034cc2..a53c4ec 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -159,6 +159,7 @@
     private final TelephonyUtil mTelephonyUtil;
     private WifiChannelUtilization mWifiChannelUtilization;
     private final KeyStore mKeyStore;
+    private final ThroughputPredictor mThroughputPredictor;
 
     public WifiInjector(Context context) {
         if (context == null) {
@@ -271,14 +272,18 @@
                 mContext.getSystemService(ActivityManager.class).isLowRamDevice() ? 256 : 512);
         mScoringParams = new ScoringParams(mContext, mFrameworkFacade, wifiHandler);
         mWifiMetrics.setScoringParams(mScoringParams);
+        mThroughputPredictor = new ThroughputPredictor(mContext);
         mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiScoreCard, mScoringParams,
-                mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiMetrics, mWifiNative);
+                mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiMetrics, mWifiNative,
+                mThroughputPredictor);
         CompatibilityScorer compatibilityScorer = new CompatibilityScorer(mScoringParams);
         mWifiNetworkSelector.registerCandidateScorer(compatibilityScorer);
         ScoreCardBasedScorer scoreCardBasedScorer = new ScoreCardBasedScorer(mScoringParams);
         mWifiNetworkSelector.registerCandidateScorer(scoreCardBasedScorer);
         BubbleFunScorer bubbleFunScorer = new BubbleFunScorer(mScoringParams);
         mWifiNetworkSelector.registerCandidateScorer(bubbleFunScorer);
+        ThroughputScorer throughputScorer = new ThroughputScorer(mScoringParams);
+        mWifiNetworkSelector.registerCandidateScorer(throughputScorer);
         mWifiMetrics.setWifiNetworkSelector(mWifiNetworkSelector);
         mSavedNetworkEvaluator = new SavedNetworkEvaluator(mContext, mScoringParams,
                 mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiConnectivityHelper,
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index 5d4a12e..f111a4c 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -37,6 +37,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
+import com.android.server.wifi.util.InformationElementUtil.BssLoad;
 import com.android.server.wifi.util.ScanResultUtil;
 import com.android.wifi.R;
 
@@ -119,6 +120,9 @@
     private final Map<String, WifiCandidates.CandidateScorer> mCandidateScorers = new ArrayMap<>();
     private boolean mIsEnhancedOpenSupportedInitialized = false;
     private boolean mIsEnhancedOpenSupported;
+    private ThroughputPredictor mThroughputPredictor;
+    private boolean mIsBluetoothConnected = false;
+    private WifiChannelUtilization mWifiChannelUtilization;
 
     /**
      * WiFi Network Selector supports various categories of networks. Each category
@@ -726,7 +730,8 @@
                                     score,
                                     (config.networkId == lastUserSelectedNetworkId)
                                             ? lastSelectionWeight : 0.0,
-                                    WifiConfiguration.isMetered(config, wifiInfo));
+                                    WifiConfiguration.isMetered(config, wifiInfo),
+                                    predictThroughput(scanDetail));
                             mWifiMetrics.setNominatorForNetwork(config.networkId,
                                     evaluatorIdToNominatorId(registeredEvaluator.getId()));
                         }
@@ -883,6 +888,27 @@
         return ans;
     }
 
+    private int predictThroughput(@NonNull ScanDetail scanDetail) {
+        if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) {
+            return 0;
+        }
+        int channelUtilizationLinkLayerStats = BssLoad.INVALID;
+        if (mWifiChannelUtilization != null) {
+            channelUtilizationLinkLayerStats =
+                    mWifiChannelUtilization.getUtilizationRatio(
+                            scanDetail.getScanResult().frequency);
+        }
+        return mThroughputPredictor.predictThroughput(
+                scanDetail.getScanResult().getWifiStandard(),
+                scanDetail.getScanResult().channelWidth,
+                scanDetail.getScanResult().level,
+                scanDetail.getScanResult().frequency,
+                scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(),
+                scanDetail.getNetworkDetail().getChannelUtilization(),
+                channelUtilizationLinkLayerStats,
+                mIsBluetoothConnected);
+    }
+
     /**
      * Register a network evaluator
      *
@@ -928,9 +954,24 @@
     private static final int ID_PREFIX = 42;
     private static final int MIN_SCORER_EXP_ID = ID_PREFIX * ID_SUFFIX_MOD;
 
+    /**
+     * Set Wifi channel utilization calculated from link layer stats
+     */
+    public void setWifiChannelUtilization(WifiChannelUtilization wifiChannelUtilization) {
+        mWifiChannelUtilization = wifiChannelUtilization;
+    }
+
+    /**
+     * Set whether bluetooth is in the connected state
+     */
+    public void setBluetoothConnected(boolean isBlueToothConnected) {
+        mIsBluetoothConnected = isBlueToothConnected;
+    }
+
     WifiNetworkSelector(Context context, WifiScoreCard wifiScoreCard, ScoringParams scoringParams,
             WifiConfigManager configManager, Clock clock, LocalLog localLog,
-            WifiMetrics wifiMetrics, WifiNative wifiNative) {
+            WifiMetrics wifiMetrics, WifiNative wifiNative,
+            ThroughputPredictor throughputPredictor) {
         mWifiConfigManager = configManager;
         mClock = clock;
         mWifiScoreCard = wifiScoreCard;
@@ -938,7 +979,7 @@
         mLocalLog = localLog;
         mWifiMetrics = wifiMetrics;
         mWifiNative = wifiNative;
-
+        mThroughputPredictor = throughputPredictor;
         mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
                 R.bool.config_wifi_framework_enable_associated_network_selection);
         mStayOnNetworkMinimumTxRate = context.getResources().getInteger(
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index ba65ba0..0b36232 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -205,6 +205,15 @@
     <!-- Indicates that wifi link probing is supported on this device -->
     <bool translatable="false" name="config_wifi_link_probing_supported">false</bool>
 
+    <!-- Indicates that 11ax mode is supported on this device -->
+    <bool translatable="false" name="config_wifi_11ax_supported">false</bool>
+
+    <!-- Indicates that contiguous 160MHz mode is supported on this device -->
+    <bool translatable="false" name="config_wifi_contiguous_160mhz_supported">false</bool>
+
+    <!-- Integer indicating the max number of spatial streams supported on this device -->
+    <integer translatable="false" name="config_wifi_max_num_spatial_stream_supported">2</integer>
+
     <!-- Configure wifi tcp buffersizes in the form:
          rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max -->
     <string name="config_wifi_tcp_buffers" translatable="false">524288,1048576,2097152,262144,524288,1048576</string>
diff --git a/service/res/values/overlayable.xml b/service/res/values/overlayable.xml
index 581f0be..af4228c 100644
--- a/service/res/values/overlayable.xml
+++ b/service/res/values/overlayable.xml
@@ -91,6 +91,9 @@
           <item type="bool" name="config_wifi_ap_mac_randomization_supported" />
           <item type="bool" name="config_wifi_aggressive_randomization_ssid_whitelist_enabled" />
           <item type="bool" name="config_wifi_link_probing_supported" />
+          <item type="bool" name="config_wifi_11ax_supported" />
+          <item type="bool" name="config_wifi_contiguous_160mhz_supported" />
+          <item type="integer" name="config_wifi_max_num_spatial_stream_supported" />
           <item type="string" name="config_wifi_tcp_buffers" />
           <item type="string" name="wifi_tether_configure_ssid_default" />
           <item type="string" name="wifi_localhotspot_configure_ssid_default" />
diff --git a/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java b/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java
index 037fd14..368ee92 100644
--- a/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/CandidateScorerTest.java
@@ -73,6 +73,13 @@
                 new BubbleFunScorer(sp),
                 sp});
 
+        sp = new ScoringParams();
+        ans.add(new Object[]{
+                "Throughput Scorer",
+                ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID,
+                new ThroughputScorer(sp),
+                sp});
+
         return ans;
     }
 
@@ -167,7 +174,7 @@
     @Test
     public void testPreferTheCurrentNetworkEvenIfRssiDifferenceIsSignificant() throws Exception {
         assertThat(evaluate(mCandidate1.setScanRssi(-74).setCurrentNetwork(true)),
-                greaterThan(evaluate(mCandidate2.setScanRssi(-65))));
+                greaterThan(evaluate(mCandidate2.setScanRssi(-70))));
     }
 
     /**
@@ -181,4 +188,16 @@
                 greaterThan(evaluate(mCandidate2.setScanRssi(unbelievablyGoodRssi))));
     }
 
+    /**
+     * Prefer high throughput network
+     */
+    @Test
+    public void testPreferHighThroughputNetwork() throws Exception {
+        if (mExpectedExpId == ThroughputScorer.THROUGHPUT_SCORER_DEFAULT_EXPID) {
+            assertThat(evaluate(mCandidate1.setScanRssi(-74)
+                            .setPredictedThroughputMbps(100)),
+                    greaterThan(evaluate(mCandidate2.setScanRssi(-74)
+                            .setPredictedThroughputMbps(50))));
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java b/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java
index 517d69a..832daf3 100644
--- a/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java
+++ b/tests/wifitests/src/com/android/server/wifi/ConcreteCandidate.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.net.MacAddress;
 import android.util.ArrayMap;
 
 import com.android.server.wifi.proto.WifiScoreCardProto;
@@ -23,7 +24,8 @@
 import java.util.Map;
 
 public final class ConcreteCandidate implements WifiCandidates.Candidate {
-    private WifiCandidates.Key mKey;
+    private WifiCandidates.Key mKey = new WifiCandidates.Key(new ScanResultMatchInfo(),
+            MacAddress.fromString("14:59:c0:51:0e:1b"), 0);
     private ScanDetail mScanDetail;
     private int mNetworkConfigId = -1;
     private boolean mIsOpenNetwork;
@@ -38,6 +40,8 @@
     private double mLastSelectionWeight;
     private int mScanRssi = -127;
     private int mFrequency = -1;
+    private int mPredictedThroughputMbps = 0;
+
     private final Map<WifiScoreCardProto.Event, WifiScoreCardProto.Signal>
             mEventStatisticsMap = new ArrayMap<>();
 
@@ -60,6 +64,7 @@
         mLastSelectionWeight = candidate.getLastSelectionWeight();
         mScanRssi = candidate.getScanRssi();
         mFrequency = candidate.getFrequency();
+        mPredictedThroughputMbps = candidate.getPredictedThroughputMbps();
         for (WifiScoreCardProto.Event event : WifiScoreCardProto.Event.values()) {
             WifiScoreCardProto.Signal signal = candidate.getEventStatistics(event);
             if (signal != null) {
@@ -218,6 +223,16 @@
         return mFrequency;
     }
 
+    public ConcreteCandidate setPredictedThroughputMbps(int predictedThroughputMbps) {
+        mPredictedThroughputMbps = predictedThroughputMbps;
+        return this;
+    }
+
+    @Override
+    public int getPredictedThroughputMbps() {
+        return mPredictedThroughputMbps;
+    }
+
     public ConcreteCandidate setEventStatistics(
             WifiScoreCardProto.Event event,
             WifiScoreCardProto.Signal signal) {
diff --git a/tests/wifitests/src/com/android/server/wifi/ThroughputPredictorTest.java b/tests/wifitests/src/com/android/server/wifi/ThroughputPredictorTest.java
new file mode 100644
index 0000000..08119e5
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/ThroughputPredictorTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.util.InformationElementUtil.BssLoad.INVALID;
+import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MAX_CHANNEL_UTILIZATION;
+import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MIN_CHANNEL_UTILIZATION;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.ScanResult;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wifi.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.ThroughputPredictor}.
+ */
+@SmallTest
+public class ThroughputPredictorTest extends WifiBaseTest {
+    @Mock private Context mContext;
+    // For simulating the resources, we use a Spy on a MockResource
+    // (which is really more of a stub than a mock, in spite if its name).
+    // This is so that we get errors on any calls that we have not explicitly set up.
+    @Spy
+    private MockResources mResource = new MockResources();
+    ThroughputPredictor mThroughputPredictor;
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(false).when(mResource).getBoolean(R.bool.config_wifi_11ax_supported);
+        doReturn(false).when(mResource).getBoolean(
+                R.bool.config_wifi_contiguous_160mhz_supported);
+        doReturn(2).when(mResource).getInteger(
+                R.integer.config_wifi_max_num_spatial_stream_supported);
+        when(mContext.getResources()).thenReturn(mResource);
+        mThroughputPredictor = new ThroughputPredictor(mContext);
+    }
+
+    /** Cleans up test. */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    @Test
+    public void verifyVeryLowRssi() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_20MHZ, -200, 2412, 1,
+                0, 0, false);
+
+        assertEquals(0, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyMaxChannelUtilizationBssLoad() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_20MHZ, 0, 2412, 1,
+                MAX_CHANNEL_UTILIZATION, 0, false);
+
+        assertEquals(0, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyMaxChannelUtilizationLinkLayerStats() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_20MHZ, 0, 5210, 1,
+                INVALID, MAX_CHANNEL_UTILIZATION, false);
+
+        assertEquals(0, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyHighRssiMinChannelUtilizationAc5g80Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_80MHZ, 0, 5180, 2,
+                MIN_CHANNEL_UTILIZATION, 50, false);
+
+        assertEquals(866, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyHighRssiMinChannelUtilizationAx5g160Mhz4ss() {
+        doReturn(true).when(mResource).getBoolean(R.bool.config_wifi_11ax_supported);
+        doReturn(true).when(mResource).getBoolean(
+                R.bool.config_wifi_contiguous_160mhz_supported);
+        doReturn(4).when(mResource).getInteger(
+                R.integer.config_wifi_max_num_spatial_stream_supported);
+        when(mContext.getResources()).thenReturn(mResource);
+        mThroughputPredictor = new ThroughputPredictor(mContext);
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AX, ScanResult.CHANNEL_WIDTH_160MHZ, 0, 5180, 4,
+                MIN_CHANNEL_UTILIZATION, INVALID, false);
+
+        assertEquals(4803, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyMidRssiMinChannelUtilizationAc5g80Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_80MHZ, -50, 5180, 2,
+                MIN_CHANNEL_UTILIZATION, INVALID, false);
+
+        assertEquals(866, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyLowRssiMinChannelUtilizationAc5g80Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_80MHZ, -80, 5180, 2,
+                MIN_CHANNEL_UTILIZATION, INVALID, false);
+
+        assertEquals(41, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyLowRssiDefaultChannelUtilizationAc5g80Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_80MHZ, -80, 5180, 2,
+                INVALID, INVALID, false);
+
+        assertEquals(31, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyHighRssiMinChannelUtilizationAc2g20Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_20MHZ, -20, 2437, 2,
+                MIN_CHANNEL_UTILIZATION, INVALID, false);
+
+        assertEquals(192, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyHighRssiMinChannelUtilizationAc2g20Mhz2ssBluetoothConnected() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_20MHZ, -20, 2437, 2,
+                MIN_CHANNEL_UTILIZATION, INVALID, true);
+
+        assertEquals(144, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyHighRssiMinChannelUtilizationLegacy5g20Mhz() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_LEGACY, ScanResult.CHANNEL_WIDTH_20MHZ, -50, 5180,
+                1, MIN_CHANNEL_UTILIZATION, INVALID, false);
+
+        assertEquals(54, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyLowRssiDefaultChannelUtilizationLegacy5g20Mhz() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_LEGACY, ScanResult.CHANNEL_WIDTH_20MHZ, -80, 5180,
+                2, INVALID, INVALID, false);
+
+        assertEquals(11, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyHighRssiMinChannelUtilizationHt2g20Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11N, ScanResult.CHANNEL_WIDTH_20MHZ, -50, 2437, 2,
+                MIN_CHANNEL_UTILIZATION, INVALID, false);
+
+        assertEquals(144, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyLowRssiDefaultChannelUtilizationHt2g20Mhz1ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11N, ScanResult.CHANNEL_WIDTH_20MHZ, -80, 2437, 1,
+                INVALID, INVALID, true);
+
+        assertEquals(5, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyHighRssiHighChannelUtilizationAx2g20Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_20MHZ, -50, 2437, 2,
+                INVALID, 80, true);
+
+        assertEquals(84, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyRssiBoundaryHighChannelUtilizationAc2g20Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_20MHZ, -69, 2437, 2,
+                INVALID, 80, true);
+
+        assertEquals(46, predictedThroughputMbps);
+    }
+
+    @Test
+    public void verifyRssiBoundaryHighChannelUtilizationAc5g40Mhz2ss() {
+        int predictedThroughputMbps = mThroughputPredictor.predictThroughput(
+                ScanResult.WIFI_STANDARD_11AC, ScanResult.CHANNEL_WIDTH_40MHZ, -66, 5180, 2,
+                INVALID, 80, false);
+
+        assertEquals(103, predictedThroughputMbps);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java b/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java
index 5c07edc..65bedc4 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiCandidatesTest.java
@@ -59,6 +59,7 @@
         MockitoAnnotations.initMocks(this);
         mWifiCandidates = new WifiCandidates(mWifiScoreCard);
         mConfig1 = WifiConfigurationTestUtil.createOpenNetwork();
+
         mScanResult1 = new ScanResult() {{
                 SSID = removeEnclosingQuotes(mConfig1.SSID);
                 capabilities = "[ESS]";
@@ -69,6 +70,7 @@
                 SSID = removeEnclosingQuotes(mConfig2.SSID);
                 capabilities = "[ESS]";
             }};
+
         doReturn(mScanResult1).when(mScanDetail1).getScanResult();
         doReturn(mScanResult2).when(mScanDetail2).getScanResult();
         doReturn(mPerBssid).when(mWifiScoreCard).lookupBssid(any(), any());
@@ -79,10 +81,10 @@
      */
     @Test
     public void testDontDieFromNulls() throws Exception {
-        mWifiCandidates.add(null, mConfig1, 1, 42, 0.0, false);
-        mWifiCandidates.add(mScanDetail1, null, 2, 16, 0.0, false);
+        mWifiCandidates.add(null, mConfig1, 1, 42, 0.0, false, 100);
+        mWifiCandidates.add(mScanDetail1, null, 2, 16, 0.0, false, 100);
         doReturn(null).when(mScanDetail2).getScanResult();
-        mWifiCandidates.add(mScanDetail2, mConfig2, 3, 314, 1.0, true);
+        mWifiCandidates.add(mScanDetail2, mConfig2, 3, 314, 1.0, true, 100);
         assertFalse(mWifiCandidates.remove(null));
 
         assertEquals(0, mWifiCandidates.size());
@@ -93,7 +95,7 @@
      */
     @Test
     public void testAddJustOne() throws Exception {
-        assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false));
+        assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false, 100));
 
         assertEquals(1, mWifiCandidates.size());
         assertEquals(0, mWifiCandidates.getFaultCount());
@@ -108,7 +110,7 @@
     public void testQuotingBotch() throws Exception {
         // Unfortunately ScanResult.SSID is not quoted; make sure we catch that
         mScanResult1.SSID = mConfig1.SSID;
-        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, true);
+        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, true, 100);
 
         // Should not have added this one
         assertEquals(0, mWifiCandidates.size());
@@ -167,7 +169,7 @@
         assertTrue(mWifiCandidates == mWifiCandidates.setPicky(true));
         try {
             mScanResult1.SSID = mConfig1.SSID; // As in testQuotingBotch()
-            mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false);
+            mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false, 100);
             fail("Exception not raised in picky mode");
         } catch (IllegalArgumentException e) {
             assertEquals(1, mWifiCandidates.getFaultCount());
@@ -181,23 +183,23 @@
     @Test
     public void testNoOverwriteCases() throws Exception {
         // Setup is to add the first candidate
-        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false);
+        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false, 100);
         assertEquals(1, mWifiCandidates.size());
 
         // Same evaluator, same score. Should not add.
-        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false));
+        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false, 100));
         assertEquals(0, mWifiCandidates.getFaultCount()); // But not considered a fault
         // Same evaluator, lower score. Should not add.
-        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 13, 0.0, false));
+        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 13, 0.0, false, 100));
         assertEquals(0, mWifiCandidates.getFaultCount()); // Also not a fault
         // Later evaluator. Should not add (regardless of score).
-        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 5, 13, 0.0, false));
-        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 5, 15, 0.0, false));
+        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 5, 13, 0.0, false, 100));
+        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 5, 15, 0.0, false, 100));
         assertEquals(0, mWifiCandidates.getFaultCount()); // Still no faults
         // Evaluator out of order. Should not add (regardless of score).
-        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 1, 12, 0.0, false));
+        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 1, 12, 0.0, false, 100));
         assertNotNull(mWifiCandidates.getLastFault()); // This one is considered a caller error
-        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 1, 15, 0.0, false));
+        assertFalse(mWifiCandidates.add(mScanDetail1, mConfig1, 1, 15, 0.0, false, 100));
         assertEquals(2, mWifiCandidates.getFaultCount());
         // After all that, only one candidate should be there.
         assertEquals(1, mWifiCandidates.size());
@@ -210,12 +212,12 @@
     public void testBssidValidation() throws Exception {
         // Null BSSID.
         mScanResult1.BSSID = null;
-        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false);
+        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false, 100);
         assertTrue("Expecting NPE, got " + mWifiCandidates.getLastFault(),
                 mWifiCandidates.getLastFault() instanceof NullPointerException);
         // Malformed BSSID
         mScanResult1.BSSID = "NotaBssid!";
-        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false);
+        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false, 100);
         assertTrue("Expecting IAE, got " + mWifiCandidates.getLastFault(),
                 mWifiCandidates.getLastFault() instanceof IllegalArgumentException);
         assertEquals(0, mWifiCandidates.size());
@@ -232,8 +234,8 @@
         mScanResult2.SSID = mScanResult1.SSID;
         mScanResult2.BSSID = mScanResult1.BSSID.replace('1', '2');
         // Add both
-        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false);
-        mWifiCandidates.add(mScanDetail2, mConfig2, 2, 14, 0.0, false);
+        mWifiCandidates.add(mScanDetail1, mConfig1, 2, 14, 0.0, false, 100);
+        mWifiCandidates.add(mScanDetail2, mConfig2, 2, 14, 0.0, false, 100);
         // We expect them both to be there
         assertEquals(2, mWifiCandidates.size());
         // But just one group
@@ -268,8 +270,8 @@
         mScanResult2.SSID = mScanResult1.SSID;
         mScanResult2.BSSID = mScanResult1.BSSID;
         // Try adding them both, the higher-scoring one second
-        assertTrue(mWifiCandidates.add(mScanDetail2, mConfig2, 2, 14, 0.0, false));
-        assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 15, 0.0, false));
+        assertTrue(mWifiCandidates.add(mScanDetail2, mConfig2, 2, 14, 0.0, false, 100));
+        assertTrue(mWifiCandidates.add(mScanDetail1, mConfig1, 2, 15, 0.0, false, 90));
         // Only one should survive
         assertEquals(1, mWifiCandidates.size());
         // And no faults
@@ -278,6 +280,6 @@
         WifiCandidates.Candidate c;
         c = mWifiCandidates.getGroupedCandidates().iterator().next().iterator().next();
         assertEquals(15, c.getEvaluatorScore());
+        assertEquals(90, c.getPredictedThroughputMbps());
     }
-
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 14ecc0c..e77cce9 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -2012,7 +2012,7 @@
     }
 
     /**
-     *  Verify that WifiChannelUtilization is updated after a scan
+     *  Verify that WifiChannelUtilization is updated
      */
     @Test
     public void verifyWifiChannelUtilizationRefreshedAfterScanResults() {
@@ -2056,4 +2056,15 @@
         verify(mWifiChannelUtilization).setDeviceMobilityState(
                 WifiManager.DEVICE_MOBILITY_STATE_STATIONARY);
     }
+
+    /**
+     *  Verify that WifiNetworkSelector sets bluetoothConnected correctly
+     */
+    @Test
+    public void verifyWifiNetworkSelectorSetBluetoothConnected() {
+        mWifiConnectivityManager.setBluetoothConnected(true);
+        verify(mWifiNS).setBluetoothConnected(true);
+        mWifiConnectivityManager.setBluetoothConnected(false);
+        verify(mWifiNS).setBluetoothConnected(false);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
index f576d2c..d9abbed 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
@@ -80,6 +80,7 @@
         setupThresholds();
 
         mLocalLog = new LocalLog(512);
+        mThroughputPredictor = new ThroughputPredictor(mContext);
 
         mWifiNetworkSelector = new WifiNetworkSelector(mContext,
                 mWifiScoreCard,
@@ -87,7 +88,9 @@
                 mWifiConfigManager, mClock,
                 mLocalLog,
                 mWifiMetrics,
-                mWifiNative);
+                mWifiNative,
+                mThroughputPredictor
+        );
         mWifiNetworkSelector.registerNetworkEvaluator(mDummyEvaluator);
         mDummyEvaluator.setEvaluatorToSelectCandidate(true);
         when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
@@ -95,6 +98,7 @@
         when(mWifiScoreCard.lookupBssid(any(), any())).thenReturn(mPerBssid);
         mCompatibilityScorer = new CompatibilityScorer(mScoringParams);
         mScoreCardBasedScorer = new ScoreCardBasedScorer(mScoringParams);
+        mThroughputScorer = new ThroughputScorer(mScoringParams);
         when(mWifiNative.getClientInterfaceName()).thenReturn("wlan0");
     }
 
@@ -210,6 +214,8 @@
     private int mStayOnNetworkMinimumRxRate;
     private CompatibilityScorer mCompatibilityScorer;
     private ScoreCardBasedScorer mScoreCardBasedScorer;
+    private ThroughputScorer mThroughputScorer;
+    private ThroughputPredictor mThroughputPredictor;
 
     private void setupContext() {
         when(mContext.getResources()).thenReturn(mResource);
@@ -228,6 +234,11 @@
                 R.integer.config_wifi_framework_min_tx_rate_for_staying_on_network, 16);
         mStayOnNetworkMinimumRxRate = setupIntegerResource(
                 R.integer.config_wifi_framework_min_rx_rate_for_staying_on_network, 16);
+        doReturn(false).when(mResource).getBoolean(R.bool.config_wifi_11ax_supported);
+        doReturn(false).when(mResource).getBoolean(
+                R.bool.config_wifi_contiguous_160mhz_supported);
+        doReturn(2).when(mResource).getInteger(
+                R.integer.config_wifi_max_num_spatial_stream_supported);
     }
 
     private void setupThresholds() {
@@ -1155,8 +1166,7 @@
         HashSet<String> blacklist = new HashSet<String>();
         // DummyNetworkEvaluator always return the first network in the scan results
         // for connection, so this should connect to the first network.
-        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(
-                scanDetails,
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
                 blacklist, mWifiInfo, false, true, true);
         assertNotNull("Result should be not null", candidate);
         WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
@@ -1582,4 +1592,25 @@
         int expid = CompatibilityScorer.COMPATIBILITY_SCORER_DEFAULT_EXPID;
         verify(mWifiMetrics, atLeastOnce()).setNetworkSelectorExperimentId(eq(expid));
     }
+
+    /**
+     * Tests that metrics are recorded for legacy scorer and throughput scorer.
+     */
+    @Test
+    public void testCandidateScorerMetricsThrougputScorer() {
+        mWifiNetworkSelector.registerCandidateScorer(mThroughputScorer);
+
+        // add a second NetworkEvaluator that returns the second network in the scan list
+        mWifiNetworkSelector.registerNetworkEvaluator(
+                new DummyNetworkEvaluator(1, DUMMY_EVALUATOR_ID_2));
+
+        test2GhzHighQuality5GhzAvailable();
+
+        int throughputExpId = experimentIdFromIdentifier(mThroughputScorer.getIdentifier());
+
+        // Wanted 2 times since test2GhzHighQuality5GhzAvailable() calls
+        // WifiNetworkSelector.selectNetwork() twice
+        verify(mWifiMetrics, times(2)).logNetworkSelectionDecision(throughputExpId,
+                WifiNetworkSelector.LEGACY_CANDIDATE_SCORER_EXP_ID, true, 2);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
index 287d0b0..74954a8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
@@ -48,7 +48,7 @@
  * Helper for WifiNetworkSelector unit tests.
  */
 public class WifiNetworkSelectorTestUtil {
-
+    private static final String TAG = "WifiNetworkSelectorTestUtil";
     /**
      * A class that holds a list of scanDetail and their associated WifiConfiguration.
      */