summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorKai Shi <kaishi@google.com>2019-11-16 04:46:33 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2019-11-16 04:46:33 +0000
commit7703348beff18ed6cfe4abec9e4d8ab227833c85 (patch)
tree276fd402836ca2191499d33e37b4475f5f6ddcfd /service
parent131b8497a10907d6db3678ab0df9c42a32c5cf8b (diff)
parent6ff1d98e75558a16574fbbbb6fe234d118678c9f (diff)
Merge "Wifi: add throughputPredictor and throughputScorer classes"
Diffstat (limited to 'service')
-rw-r--r--service/java/com/android/server/wifi/ClientModeImpl.java2
-rw-r--r--service/java/com/android/server/wifi/ThroughputPredictor.java296
-rw-r--r--service/java/com/android/server/wifi/ThroughputScorer.java143
-rw-r--r--service/java/com/android/server/wifi/WifiCandidates.java20
-rw-r--r--service/java/com/android/server/wifi/WifiConnectivityManager.java8
-rw-r--r--service/java/com/android/server/wifi/WifiInjector.java7
-rw-r--r--service/java/com/android/server/wifi/WifiNetworkSelector.java47
-rw-r--r--service/res/values/config.xml9
-rw-r--r--service/res/values/overlayable.xml3
9 files changed, 528 insertions, 7 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 68dfe083e..868865134 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -3103,6 +3103,7 @@ public class ClientModeImpl extends StateMachine {
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 @@ public class ClientModeImpl extends StateMachine {
!= 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 000000000..a8e8adac3
--- /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 000000000..1e42657c6
--- /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 00e67f44d..d0f354232 100644
--- a/service/java/com/android/server/wifi/WifiCandidates.java
+++ b/service/java/com/android/server/wifi/WifiCandidates.java
@@ -117,6 +117,10 @@ public class WifiCandidates {
*/
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 @@ public class WifiCandidates {
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 @@ public class WifiCandidates {
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 @@ public class WifiCandidates {
this.mIsCurrentNetwork = isCurrentNetwork;
this.mIsCurrentBssid = isCurrentBssid;
this.mIsMetered = isMetered;
+ this.mPredictedThroughputMbps = predictedThroughputMbps;
}
@Override
@@ -237,6 +244,11 @@ public class WifiCandidates {
return scanDetail.getScanResult().frequency;
}
+ @Override
+ public int getPredictedThroughputMbps() {
+ return mPredictedThroughputMbps;
+ }
+
/**
* Accesses statistical information from the score card
*/
@@ -359,7 +371,8 @@ public class WifiCandidates {
@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 @@ public class WifiCandidates {
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 41301385c..f74ec3f99 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -274,6 +274,13 @@ public class WifiConnectivityManager {
}
}
+ /**
+ * 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 @@ public class WifiConnectivityManager {
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 0034cc243..a53c4ec32 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -159,6 +159,7 @@ public class WifiInjector {
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 @@ public class WifiInjector {
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 5d4a12eda..f111a4cfc 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -37,6 +37,7 @@ import android.util.Pair;
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 @@ public class WifiNetworkSelector {
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 @@ public class WifiNetworkSelector {
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 @@ public class WifiNetworkSelector {
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 @@ public class WifiNetworkSelector {
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 @@ public class WifiNetworkSelector {
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 ba65ba08c..0b3623229 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 5d20c24c9..909a669eb 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" />