diff options
author | Kai Shi <kaishi@google.com> | 2019-11-13 20:40:01 -0800 |
---|---|---|
committer | Kai Shi <kaishi@google.com> | 2019-11-16 04:46:18 +0000 |
commit | 6ff1d98e75558a16574fbbbb6fe234d118678c9f (patch) | |
tree | 481fea196c9dc804e5f26bb1756100f60d744db4 | |
parent | e0c471f05a40c5dc60873d8a9e7214d947877283 (diff) |
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
16 files changed, 868 insertions, 34 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 581f0be7f..af4228c48 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 037fd14ab..368ee92cf 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 @@ public class CandidateScorerTest extends WifiBaseTest { 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 @@ public class CandidateScorerTest extends WifiBaseTest { @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 @@ public class CandidateScorerTest extends WifiBaseTest { 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 517d69aff..832daf30e 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 com.android.server.wifi.proto.WifiScoreCardProto; 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 @@ public final class ConcreteCandidate implements WifiCandidates.Candidate { 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 @@ public final class ConcreteCandidate implements WifiCandidates.Candidate { 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 @@ public final class ConcreteCandidate implements WifiCandidates.Candidate { 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 000000000..08119e59b --- /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 5c07edc27..65bedc476 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 @@ public class WifiCandidatesTest extends WifiBaseTest { MockitoAnnotations.initMocks(this); mWifiCandidates = new WifiCandidates(mWifiScoreCard); mConfig1 = WifiConfigurationTestUtil.createOpenNetwork(); + mScanResult1 = new ScanResult() {{ SSID = removeEnclosingQuotes(mConfig1.SSID); capabilities = "[ESS]"; @@ -69,6 +70,7 @@ public class WifiCandidatesTest extends WifiBaseTest { 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 @@ public class WifiCandidatesTest extends WifiBaseTest { */ @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 @@ public class WifiCandidatesTest extends WifiBaseTest { */ @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 class WifiCandidatesTest extends WifiBaseTest { 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 @@ public class WifiCandidatesTest extends WifiBaseTest { 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 @@ public class WifiCandidatesTest extends WifiBaseTest { @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 class WifiCandidatesTest extends WifiBaseTest { 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 @@ public class WifiCandidatesTest extends WifiBaseTest { 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 @@ public class WifiCandidatesTest extends WifiBaseTest { 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 @@ public class WifiCandidatesTest extends WifiBaseTest { 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 14ecc0c4c..e77cce9cb 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 @@ public class WifiConnectivityManagerTest extends WifiBaseTest { } /** - * Verify that WifiChannelUtilization is updated after a scan + * Verify that WifiChannelUtilization is updated */ @Test public void verifyWifiChannelUtilizationRefreshedAfterScanResults() { @@ -2056,4 +2056,15 @@ public class WifiConnectivityManagerTest extends WifiBaseTest { 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 f576d2ce3..d9abbed12 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 @@ public class WifiNetworkSelectorTest extends WifiBaseTest { setupThresholds(); mLocalLog = new LocalLog(512); + mThroughputPredictor = new ThroughputPredictor(mContext); mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiScoreCard, @@ -87,7 +88,9 @@ public class WifiNetworkSelectorTest extends WifiBaseTest { mWifiConfigManager, mClock, mLocalLog, mWifiMetrics, - mWifiNative); + mWifiNative, + mThroughputPredictor + ); mWifiNetworkSelector.registerNetworkEvaluator(mDummyEvaluator); mDummyEvaluator.setEvaluatorToSelectCandidate(true); when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()); @@ -95,6 +98,7 @@ public class WifiNetworkSelectorTest extends WifiBaseTest { 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 @@ public class WifiNetworkSelectorTest extends WifiBaseTest { 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 @@ public class WifiNetworkSelectorTest extends WifiBaseTest { 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 @@ public class WifiNetworkSelectorTest extends WifiBaseTest { 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 @@ public class WifiNetworkSelectorTest extends WifiBaseTest { 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 287d0b0d8..74954a875 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 @@ import java.util.Set; * 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. */ |