From aa5fd51758c9d2d4c66a8774987cd1ebc5286c22 Mon Sep 17 00:00:00 2001 From: Michael Plass Date: Wed, 7 Mar 2018 14:59:34 -0800 Subject: [wifi_score_params] elaborations Add ability to track changes to Settings.Global.WIFI_SCORE_PARAMS Not yet wired into framework Bug: 65216267 Test: Unit tests Change-Id: Iab69e1d92f26c9dda9574a9187aee6a9474665f0 --- .../com/android/server/wifi/ScoringParams.java | 181 ++++++++++++++++-- .../com/android/server/wifi/ScoringParamsTest.java | 204 +++++++++++++++++---- 2 files changed, 334 insertions(+), 51 deletions(-) diff --git a/service/java/com/android/server/wifi/ScoringParams.java b/service/java/com/android/server/wifi/ScoringParams.java index 71ec78151..1fddd7688 100644 --- a/service/java/com/android/server/wifi/ScoringParams.java +++ b/service/java/com/android/server/wifi/ScoringParams.java @@ -16,7 +16,13 @@ package com.android.server.wifi; +import android.annotation.NonNull; import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.util.KeyValueListParser; +import android.util.Log; import com.android.internal.R; @@ -28,36 +34,167 @@ import com.android.internal.R; * */ public class ScoringParams { + private static final String TAG = "WifiScoringParams"; private static final int EXIT = 0; private static final int ENTRY = 1; private static final int SUFFICIENT = 2; private static final int GOOD = 3; - private final int[] mRssi2 = {-83, -80, -73, -60}; - private final int[] mRssi5 = {-80, -77, -70, -57}; + + /** + * Parameter values are stored in a separate container so that a new collection of values can + * be checked for consistency before activating them. + */ + private class Values { + public static final String KEY_RSSI2 = "rssi2"; + public static final String KEY_RSSI5 = "rssi5"; + public static final String KEY_HORIZON = "horizon"; // number of seconds for rssi forecast + + public final int[] rssi2 = {-83, -80, -73, -60}; + public final int[] rssi5 = {-80, -77, -70, -57}; + public int horizon = 15; + + Values() { + } + + Values(Values source) { + for (int i = 0; i < rssi2.length; i++) { + rssi2[i] = source.rssi2[i]; + } + for (int i = 0; i < rssi5.length; i++) { + rssi5[i] = source.rssi5[i]; + } + horizon = source.horizon; + } + + public void parseString(String kvList) throws IllegalArgumentException { + KeyValueListParser parser = new KeyValueListParser(','); + parser.setString(kvList); + updateIntArray(rssi2, parser, KEY_RSSI2); + updateIntArray(rssi5, parser, KEY_RSSI5); + horizon = updateInt(parser, KEY_HORIZON, horizon); + } + + private int updateInt(KeyValueListParser parser, String key, int defaultValue) + throws IllegalArgumentException { + String value = parser.getString(key, null); + if (value == null) return defaultValue; + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + } + + private void updateIntArray(final int[] dest, KeyValueListParser parser, String key) + throws IllegalArgumentException { + if (parser.getString(key, null) == null) return; + int[] ints = parser.getIntArray(key, null); + if (ints == null) throw new IllegalArgumentException(); + if (ints.length != dest.length) throw new IllegalArgumentException(); + for (int i = 0; i < dest.length; i++) { + dest[i] = ints[i]; + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + appendKey(sb, KEY_RSSI2); + appendInts(sb, rssi2); + appendKey(sb, KEY_RSSI5); + appendInts(sb, rssi5); + appendKey(sb, KEY_HORIZON); + sb.append(horizon); + return sb.toString(); + } + + private void appendKey(StringBuilder sb, String key) { + if (sb.length() != 0) sb.append(","); + sb.append(key).append("="); + } + + private void appendInts(StringBuilder sb, final int[] a) { + final int n = a.length; + for (int i = 0; i < n; i++) { + if (i > 0) sb.append(":"); + sb.append(a[i]); + } + } + } + + @NonNull private Values mVal = new Values(); public ScoringParams() { } public ScoringParams(Context context) { - mRssi2[EXIT] = context.getResources().getInteger( + loadResources(context); + } + + public ScoringParams(Context context, FrameworkFacade facade, Handler handler) { + loadResources(context); + setupContentObserver(context, facade, handler); + } + + private void loadResources(Context context) { + mVal.rssi2[EXIT] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz); - mRssi2[ENTRY] = context.getResources().getInteger( + mVal.rssi2[ENTRY] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_24GHz); - mRssi2[SUFFICIENT] = context.getResources().getInteger( + mVal.rssi2[SUFFICIENT] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz); - mRssi2[GOOD] = context.getResources().getInteger( + mVal.rssi2[GOOD] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); - mRssi5[EXIT] = context.getResources().getInteger( + mVal.rssi5[EXIT] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz); - mRssi5[ENTRY] = context.getResources().getInteger( + mVal.rssi5[ENTRY] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_5GHz); - mRssi5[SUFFICIENT] = context.getResources().getInteger( + mVal.rssi5[SUFFICIENT] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz); - mRssi5[GOOD] = context.getResources().getInteger( + mVal.rssi5[GOOD] = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz); } - private static final int MINIMUM_5GHZ_BAND_FREQUENCY_IN_MEGAHERTZ = 5000; + private void setupContentObserver(Context context, FrameworkFacade facade, Handler handler) { + final ScoringParams self = this; + String defaults = self.toString(); + ContentObserver observer = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + String params = facade.getStringSetting( + context, Settings.Global.WIFI_SCORE_PARAMS); + if (params != null) { + self.update(defaults); + if (!self.update(params)) { + Log.e(TAG, "Error in " + Settings.Global.WIFI_SCORE_PARAMS + ": " + params); + } + } + Log.i(TAG, self.toString()); + } + }; + facade.registerContentObserver(context, + Settings.Global.getUriFor(Settings.Global.WIFI_SCORE_PARAMS), + true, + observer); + observer.onChange(false); + } + + /** + * Updates the parameters from the given parameter string. + * If any errors are detected, no change is made. + * @param kvList is a comma-separated key=value list. + * @return true for success + */ + public boolean update(String kvList) { + Values v = new Values(mVal); + try { + v.parseString(kvList); + mVal = v; + return true; + } catch (IllegalArgumentException e) { + return false; + } + } /** Constant to denote someplace in the 2.4 GHz band */ public static final int BAND2 = 2400; @@ -95,11 +232,25 @@ public class ScoringParams { return getRssiArray(frequencyMegaHertz)[GOOD]; } - private int[] getRssiArray(int frequencyMegaHertz) { - if (frequencyMegaHertz < MINIMUM_5GHZ_BAND_FREQUENCY_IN_MEGAHERTZ) { - return mRssi2; + /** + * Returns the number of seconds to use for rssi forecast. + */ + public int getHorizonSeconds() { + return mVal.horizon; + } + + private static final int MINIMUM_5GHZ_BAND_FREQUENCY_IN_MEGAHERTZ = 5000; + + private int[] getRssiArray(int frequency) { + if (frequency < MINIMUM_5GHZ_BAND_FREQUENCY_IN_MEGAHERTZ) { + return mVal.rssi2; } else { - return mRssi5; + return mVal.rssi5; } } + + @Override + public String toString() { + return mVal.toString(); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java b/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java index 5047701cf..6b3b720e9 100644 --- a/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java +++ b/tests/wifitests/src/com/android/server/wifi/ScoringParamsTest.java @@ -17,18 +17,27 @@ 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.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; import android.support.test.filters.SmallTest; import com.android.internal.R; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -41,6 +50,111 @@ public class ScoringParamsTest { ScoringParams mScoringParams; + /** + * Sets up for unit test + */ + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + setUpResources(mResources); + when(mContext.getResources()).thenReturn(mResources); + mScoringParams = new ScoringParams(); + } + + /** + * Check that thresholds are properly ordered, and in range. + */ + private void checkThresholds(int frequency) { + assertTrue(-127 <= mScoringParams.getExitRssi(frequency)); + assertTrue(mScoringParams.getExitRssi(frequency) + <= mScoringParams.getEntryRssi(frequency)); + assertTrue(mScoringParams.getEntryRssi(frequency) + <= mScoringParams.getSufficientRssi(frequency)); + assertTrue(mScoringParams.getSufficientRssi(frequency) + <= mScoringParams.getGoodRssi(frequency)); + assertTrue(mScoringParams.getGoodRssi(frequency) < 0); + } + + /** + * Test basic constructor + */ + @Test + public void testBasicConstructor() throws Exception { + mScoringParams = new ScoringParams(); + checkThresholds(2412); + checkThresholds(5020); + assertEquals(15, mScoringParams.getHorizonSeconds()); + } + + /** + * Test toString + */ + @Test + public void testToString() throws Exception { + mScoringParams = new ScoringParams(); + String expect = "rssi2=-83:-80:-73:-60,rssi5=-80:-77:-70:-57,horizon=15"; + String actual = mScoringParams.toString(); + assertEquals(expect, actual); + } + + /** + * Test complete update + */ + @Test + public void testUpdateEverything() throws Exception { + mScoringParams = new ScoringParams(); + String params = "rssi2=-86:-84:-77:-10,rssi5=-88:-77:-66:-55,horizon=3"; + assertTrue(mScoringParams.update(params)); + assertEquals(params, mScoringParams.toString()); + } + + /** + * Test partial update + */ + @Test + public void testPartialUpdate() throws Exception { + mScoringParams = new ScoringParams(); + String before = mScoringParams.toString(); + String partial = "rssi5=-88:-77:-66:-55"; + assertFalse(before.contains(partial)); + assertTrue(mScoringParams.update(partial)); + String after = mScoringParams.toString(); + assertTrue(after + " should contain " + partial, after.contains(partial)); + } + + /** + * Test some failed updates + */ + @Test + public void testUpdateFail() throws Exception { + mScoringParams = new ScoringParams(); + String before = mScoringParams.toString(); + assertFalse(mScoringParams.update("word")); + assertFalse(mScoringParams.update("42")); + assertFalse(mScoringParams.update(" ")); + assertFalse(mScoringParams.update("horizon=flat")); + assertFalse(mScoringParams.update(",,,,,,,,,,,,,,,,,,")); + assertFalse(mScoringParams.update("rssi2=-86")); + assertFalse(mScoringParams.update("rssi2=-99:-88:-77:-66:-55")); + assertFalse(mScoringParams.update("rssi5=one:two:three:four")); + assertEquals(before, mScoringParams.toString()); + } + + /** + * Test that empty updates are OK + */ + @Test + public void testEmptyUpdate() throws Exception { + mScoringParams = new ScoringParams(); + String before = mScoringParams.toString(); + assertTrue(mScoringParams.update("")); + assertTrue(mScoringParams.update(null)); + assertEquals(before, mScoringParams.toString()); + } + + /** + * Tests for obtaining values from device configuration (config.xml) + */ int mBad2GHz, mEntry2GHz, mSufficient2GHz, mGood2GHz; int mBad5GHz, mEntry5GHz, mSufficient5GHz, mGood5GHz; @@ -76,46 +190,11 @@ public class ScoringParamsTest { R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz, -50); } - /** - * Sets up for unit test - */ - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - setUpResources(mResources); - when(mContext.getResources()).thenReturn(mResources); - mScoringParams = new ScoringParams(); - } - - /** - * Check that thresholds are properly ordered, and in range. - */ - private void checkThresholds(int frequency) { - assertTrue(-127 <= mScoringParams.getExitRssi(frequency)); - assertTrue(mScoringParams.getExitRssi(frequency) - <= mScoringParams.getEntryRssi(frequency)); - assertTrue(mScoringParams.getEntryRssi(frequency) - <= mScoringParams.getSufficientRssi(frequency)); - assertTrue(mScoringParams.getSufficientRssi(frequency) - <= mScoringParams.getGoodRssi(frequency)); - assertTrue(mScoringParams.getGoodRssi(frequency) < 0); - } - - /** - * Test basic constuctor - */ - @Test - public void testBasicConstructor() { - mScoringParams = new ScoringParams(); - checkThresholds(2412); - checkThresholds(5020); - } - /** * Check that we get the config.xml values, if that's what we want */ @Test - public void testContextConstructor() { + public void testContextConstructor() throws Exception { mScoringParams = new ScoringParams(mContext); assertEquals(mBad2GHz, mScoringParams.getExitRssi(2412)); @@ -130,4 +209,57 @@ public class ScoringParamsTest { assertEquals(mGood5GHz, mScoringParams.getGoodRssi(5678)); assertEquals(mGood5GHz, mScoringParams.getGoodRssi(ScoringParams.BAND5)); } + + /** + * Additional mocks for handling Settings + */ + @Mock FrameworkFacade mFrameworkFacade; + @Mock Handler mHandler; + + /** + * Test getting updates from Settings + * + * Exercises the ContentObserver notification path + */ + @Test + public void testFullConstructorWithUpdatesFromSettings() throws Exception { + ArgumentCaptor captor = ArgumentCaptor.forClass(ContentObserver.class); + when(mFrameworkFacade.getStringSetting(mContext, Settings.Global.WIFI_SCORE_PARAMS)) + .thenReturn(null); + mScoringParams = new ScoringParams(mContext, mFrameworkFacade, mHandler); + verify(mFrameworkFacade) + .registerContentObserver(eq(mContext), any(), anyBoolean(), captor.capture()); + + String before = mScoringParams.toString(); + String changed = before.replace('8', '9'); + assertFalse(changed.equals(before)); + + when(mFrameworkFacade.getStringSetting(mContext, Settings.Global.WIFI_SCORE_PARAMS)) + .thenReturn(changed); + captor.getValue().onChange(/*selfChange*/ false); + assertEquals(changed, mScoringParams.toString()); + + when(mFrameworkFacade.getStringSetting(mContext, Settings.Global.WIFI_SCORE_PARAMS)) + .thenReturn(""); + captor.getValue().onChange(/*selfChange*/ false); + assertEquals(before, mScoringParams.toString()); + } + + @Test + public void testBadSettings() throws Exception { + ArgumentCaptor captor = ArgumentCaptor.forClass(ContentObserver.class); + when(mFrameworkFacade.getStringSetting(mContext, Settings.Global.WIFI_SCORE_PARAMS)) + .thenReturn(null); + mScoringParams = new ScoringParams(mContext, mFrameworkFacade, mHandler); + verify(mFrameworkFacade) + .registerContentObserver(eq(mContext), any(), anyBoolean(), captor.capture()); + + String before = mScoringParams.toString(); + String garbage = "what??"; + + when(mFrameworkFacade.getStringSetting(mContext, Settings.Global.WIFI_SCORE_PARAMS)) + .thenReturn(garbage); + captor.getValue().onChange(/*selfChange*/ false); + assertEquals(before, mScoringParams.toString()); + } } -- cgit v1.2.3