| /* |
| * Copyright (C) 2016 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 org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.AdditionalAnswers.answerVoid; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.Mockito.anyInt; |
| import static org.mockito.Mockito.atLeast; |
| import static org.mockito.Mockito.atMost; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.net.NetworkAgent; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiInfo; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.internal.R; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.io.PrintWriter; |
| |
| /** |
| * Unit tests for {@link com.android.server.wifi.WifiScoreReport}. |
| */ |
| @SmallTest |
| public class WifiScoreReportTest { |
| |
| |
| class FakeClock extends Clock { |
| long mWallClockMillis = 1500000000000L; |
| int mStepMillis = 1001; |
| |
| @Override |
| public long getWallClockMillis() { |
| mWallClockMillis += mStepMillis; |
| return mWallClockMillis; |
| } |
| } |
| |
| FakeClock mClock; |
| WifiConfiguration mWifiConfiguration; |
| WifiScoreReport mWifiScoreReport; |
| ScanDetailCache mScanDetailCache; |
| WifiInfo mWifiInfo; |
| ScoringParams mScoringParams; |
| @Mock Context mContext; |
| @Mock NetworkAgent mNetworkAgent; |
| @Mock Resources mResources; |
| @Mock WifiMetrics mWifiMetrics; |
| @Mock PrintWriter mPrintWriter; |
| |
| /** |
| * Sets up resource values for testing |
| * |
| * See frameworks/base/core/res/res/values/config.xml |
| */ |
| private void setUpResources(Resources resources) { |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz)) |
| .thenReturn(-82); |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_5GHz)) |
| .thenReturn(-77); |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz)) |
| .thenReturn(-70); |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz)) |
| .thenReturn(-57); |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz)) |
| .thenReturn(-85); |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_24GHz)) |
| .thenReturn(-80); |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz)) |
| .thenReturn(-73); |
| when(resources.getInteger( |
| R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz)) |
| .thenReturn(-60); |
| } |
| |
| /** |
| * Sets up for unit test |
| */ |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| setUpResources(mResources); |
| mWifiInfo = new WifiInfo(); |
| mWifiInfo.setFrequency(2412); |
| int maxSize = 10; |
| int trimSize = 5; |
| when(mContext.getResources()).thenReturn(mResources); |
| mClock = new FakeClock(); |
| mScoringParams = new ScoringParams(mContext); |
| mWifiScoreReport = new WifiScoreReport(mScoringParams, mClock); |
| } |
| |
| /** |
| * Cleans up after test |
| */ |
| @After |
| public void tearDown() throws Exception { |
| mResources = null; |
| mWifiScoreReport = null; |
| mWifiMetrics = null; |
| } |
| |
| /** |
| * Test for score reporting |
| * |
| * The score should be sent to both the NetworkAgent and the |
| * WifiMetrics |
| */ |
| @Test |
| public void calculateAndReportScoreSucceeds() throws Exception { |
| mWifiInfo.setRssi(-77); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| verify(mNetworkAgent).sendNetworkScore(anyInt()); |
| verify(mWifiMetrics).incrementWifiScoreCount(anyInt()); |
| } |
| |
| /** |
| * Test for no score report if rssi is invalid |
| * |
| * The score should be sent to neither the NetworkAgent nor the |
| * WifiMetrics |
| */ |
| @Test |
| public void calculateAndReportScoreDoesNotReportWhenRssiIsNotValid() throws Exception { |
| mWifiInfo.setRssi(WifiInfo.INVALID_RSSI); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| verify(mNetworkAgent, never()).sendNetworkScore(anyInt()); |
| verify(mWifiMetrics, never()).incrementWifiScoreCount(anyInt()); |
| } |
| |
| /** |
| * Test for operation with null NetworkAgent |
| * |
| * Expect to not die, and to calculate the score and report to metrics. |
| */ |
| @Test |
| public void networkAgentMayBeNull() throws Exception { |
| mWifiInfo.setRssi(-33); |
| mWifiScoreReport.enableVerboseLogging(true); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, null, mWifiMetrics); |
| verify(mWifiMetrics).incrementWifiScoreCount(anyInt()); |
| } |
| |
| /** |
| * Exercise the rates with low RSSI |
| * |
| * The setup has a low (not bad) RSSI, and data movement (txSuccessRate) above |
| * the threshold. |
| * |
| * Expect a score above threshold. |
| */ |
| @Test |
| public void allowLowRssiIfDataIsMoving() throws Exception { |
| mWifiInfo.setRssi(-80); |
| mWifiInfo.setLinkSpeed(6); // Mbps |
| mWifiInfo.txSuccessRate = 5.1; // proportional to pps |
| mWifiInfo.rxSuccessRate = 5.1; |
| for (int i = 0; i < 10; i++) { |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| int score = mWifiInfo.score; |
| assertTrue(score > ConnectedScore.WIFI_TRANSITION_SCORE); |
| } |
| |
| /** |
| * Bad RSSI without data moving should allow handoff |
| * |
| * The setup has a bad RSSI, and the txSuccessRate is below threshold; several |
| * scoring iterations are performed. |
| * |
| * Expect the score to drop below the handoff threshold. |
| */ |
| @Test |
| public void giveUpOnBadRssiWhenDataIsNotMoving() throws Exception { |
| mWifiInfo.setRssi(-100); |
| mWifiInfo.setLinkSpeed(6); // Mbps |
| mWifiInfo.setFrequency(5220); |
| mWifiScoreReport.enableVerboseLogging(true); |
| mWifiInfo.txSuccessRate = 0.1; |
| mWifiInfo.rxSuccessRate = 0.1; |
| for (int i = 0; i < 10; i++) { |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| int score = mWifiInfo.score; |
| assertTrue(score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| verify(mNetworkAgent, atLeast(1)).sendNetworkScore(score); |
| } |
| |
| /** |
| * When the score ramps down to the exit theshold, let go. |
| */ |
| @Test |
| public void giveUpOnBadRssiAggressively() throws Exception { |
| String oops = "giveUpOnBadRssiAggressively"; |
| mWifiInfo.setFrequency(5220); |
| for (int rssi = -60; rssi >= -83; rssi -= 1) { |
| mWifiInfo.setRssi(rssi); |
| oops += " " + mClock.mWallClockMillis + "," + rssi; |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| oops += ":" + mWifiInfo.score; |
| } |
| int score = mWifiInfo.score; |
| verify(mNetworkAgent, atLeast(1)).sendNetworkScore(score); |
| assertTrue(oops, score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| } |
| |
| /** |
| * RSSI that falls rapidly but does not cross entry threshold should not cause handoff |
| * |
| * Expect the score to not drop below the handoff threshold. |
| */ |
| @Test |
| public void stayOnIfRssiDoesNotGetBelowEntryThreshold() throws Exception { |
| String oops = "didNotStickLanding"; |
| int minScore = 100; |
| mWifiInfo.setLinkSpeed(6); // Mbps |
| mWifiInfo.setFrequency(5220); |
| mWifiScoreReport.enableVerboseLogging(true); |
| mWifiInfo.txSuccessRate = 0.1; |
| mWifiInfo.rxSuccessRate = 0.1; |
| assertTrue(mScoringParams.update("rssi5=-83:-80:-66:-55")); |
| for (int r = -30; r >= -100; r -= 1) { |
| int rssi = Math.max(r, -80); |
| mWifiInfo.setRssi(rssi); |
| oops += " " + mClock.mWallClockMillis + "," + rssi; |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| oops += ":" + mWifiInfo.score; |
| if (mWifiInfo.score < minScore) minScore = mWifiInfo.score; |
| } |
| assertTrue(oops, minScore > ConnectedScore.WIFI_TRANSITION_SCORE); |
| } |
| |
| /** |
| * Don't breach if the success rates are great |
| * |
| * Ramp the RSSI down, but maintain a high packet throughput |
| * |
| * Expect score to stay above above threshold. |
| */ |
| @Test |
| public void allowTerribleRssiIfDataIsMovingWell() throws Exception { |
| mWifiInfo.txSuccessRate = mScoringParams.getYippeeSkippyPacketsPerSecond() + 0.1; |
| mWifiInfo.rxSuccessRate = mScoringParams.getYippeeSkippyPacketsPerSecond() + 0.1; |
| assertTrue(mWifiInfo.txSuccessRate > 10); |
| mWifiInfo.setFrequency(5220); |
| for (int r = -30; r >= -120; r -= 2) { |
| mWifiInfo.setRssi(r); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| assertTrue(mWifiInfo.score > ConnectedScore.WIFI_TRANSITION_SCORE); |
| } |
| // If the throughput dips, we should let go |
| mWifiInfo.rxSuccessRate = mScoringParams.getYippeeSkippyPacketsPerSecond() - 0.1; |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| assertTrue(mWifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| // And even if throughput improves again, once we have decided to let go, disregard |
| // the good rates. |
| mWifiInfo.rxSuccessRate = mScoringParams.getYippeeSkippyPacketsPerSecond() + 0.1; |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| assertTrue(mWifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| } |
| |
| /** |
| * Never ask for nud check when nud=0 |
| */ |
| @Test |
| public void neverAskForNudCheckWhenNudKnobIsZero() throws Exception { |
| assertTrue(mScoringParams.update("nud=0")); |
| assertEquals(0, mScoringParams.getNudKnob()); |
| mWifiInfo.setFrequency(5220); |
| for (int rssi = -30; rssi >= -120; rssi -= 1) { |
| mWifiInfo.setRssi(rssi); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| assertFalse(mWifiScoreReport.shouldCheckIpLayer()); |
| } |
| } |
| |
| /** |
| * Eventually ask for nud check when nud=1 |
| */ |
| @Test |
| public void eventuallyAskForNudCheckWhenNudKnobIsOne() throws Exception { |
| String oops = "nud=1"; |
| long lastAskedMillis = 0; // Check that we don't send too soon |
| int asks = 0; // Keep track of how many time we asked |
| assertTrue(mScoringParams.update("nud=1")); |
| assertEquals(1, mScoringParams.getNudKnob()); |
| mWifiInfo.setFrequency(5220); |
| for (int rssi = -40; rssi >= -120; rssi -= 1) { |
| mWifiInfo.setRssi(rssi); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| boolean ask = mWifiScoreReport.shouldCheckIpLayer(); |
| if (ask) { |
| assertTrue(mWifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| assertTrue(oops, mClock.mWallClockMillis >= lastAskedMillis + 5000); |
| lastAskedMillis = mClock.mWallClockMillis; |
| oops += " " + lastAskedMillis + ":" + mWifiInfo.score; |
| mWifiScoreReport.noteIpCheck(); |
| asks++; |
| } |
| } |
| assertTrue(oops + " asks:" + asks, asks > 5 && asks < 12); |
| } |
| |
| |
| /** |
| * Ask for more nud checks when nud=10 |
| */ |
| @Test |
| public void askForMoreNudChecksWhenNudKnobIsBigger() throws Exception { |
| String oops = "nud=10"; |
| long lastAskedMillis = 0; // Check that we don't send too soon |
| int asks = 0; // Keep track of how many time we asked |
| assertTrue(mScoringParams.update("nud=10")); |
| assertEquals(10, mScoringParams.getNudKnob()); |
| mWifiInfo.setFrequency(5220); |
| for (int rssi = -40; rssi >= -120; rssi -= 1) { |
| mWifiInfo.setRssi(rssi); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| boolean ask = mWifiScoreReport.shouldCheckIpLayer(); |
| if (ask) { |
| assertTrue(mWifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| assertTrue(oops, mClock.mWallClockMillis >= lastAskedMillis + 5000); |
| lastAskedMillis = mClock.mWallClockMillis; |
| oops += " " + lastAskedMillis + ":" + mWifiInfo.score; |
| mWifiScoreReport.noteIpCheck(); |
| asks++; |
| } |
| } |
| assertTrue(oops + " asks:" + asks, asks > 12 && asks < 80); |
| } |
| |
| /** |
| * Test initial conditions, and after reset() |
| */ |
| @Test |
| public void exerciseReset() throws Exception { |
| assertFalse(mWifiScoreReport.shouldCheckIpLayer()); |
| mWifiScoreReport.reset(); |
| assertFalse(mWifiScoreReport.shouldCheckIpLayer()); |
| } |
| |
| /** |
| * This setup causes some reports to be generated when println |
| * methods are called, to check for "concurrent" modification |
| * errors. |
| */ |
| private void setupToGenerateAReportWhenPrintlnIsCalled() { |
| int[] counter = new int[1]; |
| doAnswer(answerVoid((String line) -> { |
| if (counter[0]++ < 3) { |
| mWifiScoreReport.calculateAndReportScore( |
| mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| })).when(mPrintWriter).println(anyString()); |
| } |
| |
| /** |
| * Test data logging |
| */ |
| @Test |
| public void testDataLogging() throws Exception { |
| for (int i = 0; i < 10; i++) { |
| mWifiInfo.setRssi(-65 + i); |
| mWifiInfo.setLinkSpeed(300); |
| mWifiInfo.setFrequency(5220); |
| mWifiInfo.txSuccessRate = 0.1 + i; |
| mWifiInfo.txRetriesRate = 0.2 + i; |
| mWifiInfo.txBadRate = 0.01 * i; |
| mWifiInfo.rxSuccessRate = 0.3 + i; |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| setupToGenerateAReportWhenPrintlnIsCalled(); |
| mWifiScoreReport.dump(null, mPrintWriter, null); |
| verify(mPrintWriter, times(11)).println(anyString()); |
| } |
| |
| /** |
| * Test data logging limit |
| * <p> |
| * Check that only a bounded amount of data is collected for dumpsys report |
| */ |
| @Test |
| public void testDataLoggingLimit() throws Exception { |
| for (int i = 0; i < 3620; i++) { |
| mWifiInfo.setRssi(-65 + i % 20); |
| mWifiInfo.setLinkSpeed(300); |
| mWifiInfo.setFrequency(5220); |
| mWifiInfo.txSuccessRate = 0.1 + i % 100; |
| mWifiInfo.txRetriesRate = 0.2 + i % 100; |
| mWifiInfo.txBadRate = 0.0001 * i; |
| mWifiInfo.rxSuccessRate = 0.3 + i % 200; |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| mWifiScoreReport.dump(null, mPrintWriter, null); |
| verify(mPrintWriter, atMost(3601)).println(anyString()); |
| } |
| |
| /** |
| * Test for staying at below transition score for a certain period of time. |
| */ |
| @Test |
| public void stayAtBelowTransitionScoreForCertainPeriodOfTime() throws Exception { |
| mWifiScoreReport.enableVerboseLogging(true); |
| mWifiInfo.setFrequency(5220); |
| |
| // Reduce RSSI value to fall below the transition score |
| for (int rssi = -60; rssi >= -83; rssi -= 1) { |
| mWifiInfo.setRssi(rssi); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| assertTrue(mWifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| |
| // Then, set high RSSI value to exceed the transition score |
| mWifiInfo.setRssi(-50); |
| // 8 seconds elapse |
| for (int i = 0; i < 8; i++) { |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| assertTrue(mWifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| |
| // 9 seconds elapse |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| assertTrue(mWifiInfo.score > ConnectedScore.WIFI_TRANSITION_SCORE); |
| } |
| |
| /** |
| * Test for resetting the internal timer which is used to keep staying at |
| * below transition score for a certain period of time. |
| */ |
| @Test |
| public void stayAtBelowTransitionScoreWithReset() throws Exception { |
| mWifiScoreReport.enableVerboseLogging(true); |
| mWifiInfo.setFrequency(5220); |
| |
| // Reduce RSSI value to fall below the transition score |
| for (int rssi = -60; rssi >= -83; rssi -= 1) { |
| mWifiInfo.setRssi(rssi); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| } |
| assertTrue(mWifiInfo.score < ConnectedScore.WIFI_TRANSITION_SCORE); |
| |
| // Then, set high RSSI value to exceed the transition score |
| mWifiInfo.setRssi(-50); |
| // Reset the internal timer so that no need to wait for 9 seconds |
| mWifiScoreReport.reset(); |
| mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics); |
| assertTrue(mWifiInfo.score > ConnectedScore.WIFI_TRANSITION_SCORE); |
| } |
| } |