Implement score cache eviction in AccessPoint.
This prevents cached scores from being held indefinitely and used for
SSID fallback logic in WifiTracker (Picker).
Bug: 63073866
Test: runtest --path frameworks/base/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
Change-Id: Ib351d20db30dfd18b69bb1f8e4d4f26fc6b74ef0
Merged-In: Ib351d20db30dfd18b69bb1f8e4d4f26fc6b74ef0
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 0de3e52..330eaf0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -51,6 +51,7 @@
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.Log;
@@ -130,11 +131,14 @@
* <p>This cache should not be evicted with scan results, as the values here are used to
* generate a fallback in the absence of scores for the visible APs.
*/
- // TODO(b/63073866): change this to have score eviction logic
- private final Map<String, ScoredNetwork> mScoredNetworkCache = new HashMap<>();
+ private final Map<String, TimestampedScoredNetwork> mScoredNetworkCache = new HashMap<>();
+
+ /** Maximum age in millis of cached scored networks in {@link #mScoredNetworkCache}. */
+ @VisibleForTesting static final long MAX_CACHED_SCORE_AGE_MILLIS =
+ 24 * DateUtils.DAY_IN_MILLIS;
/** Maximum age of scan results to hold onto while actively scanning. **/
- private static final long MAX_SCAN_RESULT_AGE_MS = 15000;
+ private static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000;
static final String KEY_NETWORKINFO = "key_networkinfo";
static final String KEY_WIFIINFO = "key_wifiinfo";
@@ -246,10 +250,10 @@
}
}
if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) {
- ArrayList<ScoredNetwork> scoredNetworkArrayList =
+ ArrayList<TimestampedScoredNetwork> scoredNetworkArrayList =
savedState.getParcelableArrayList(KEY_SCOREDNETWORKCACHE);
- for (ScoredNetwork score : scoredNetworkArrayList) {
- mScoredNetworkCache.put(score.networkKey.wifiKey.bssid, score);
+ for (TimestampedScoredNetwork timedScore : scoredNetworkArrayList) {
+ mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore);
}
}
if (savedState.containsKey(KEY_FQDN)) {
@@ -448,20 +452,40 @@
/**
* Updates the AccessPoint rankingScore and speed, returning true if the data has changed.
*
+ * <p>Any cached {@link TimestampedScoredNetwork} objects older than
+ * {@link #MAX_CACHED_SCORE_AGE_MILLIS} will be removed when this method is invoked.
+ *
* <p>Precondition: {@link #mRssi} is up to date before invoking this method.
*
* @param scoreCache The score cache to use to retrieve scores.
* @return true if the set speed has changed
*/
private boolean updateScores(WifiNetworkScoreCache scoreCache) {
+ long nowMillis = SystemClock.elapsedRealtime();
for (ScanResult result : mScanResultCache.values()) {
ScoredNetwork score = scoreCache.getScoredNetwork(result);
if (score == null) {
continue;
}
- mScoredNetworkCache.put(result.BSSID, score);
+ TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
+ if (timedScore == null) {
+ mScoredNetworkCache.put(
+ result.BSSID, new TimestampedScoredNetwork(score, nowMillis));
+ } else {
+ // Update data since the has been seen in the score cache
+ timedScore.update(score, nowMillis);
+ }
}
+ // Remove old cached networks
+ long evictionCutoff = nowMillis - MAX_CACHED_SCORE_AGE_MILLIS;
+ Iterator<TimestampedScoredNetwork> iterator = mScoredNetworkCache.values().iterator();
+ iterator.forEachRemaining(timestampedScoredNetwork -> {
+ if (timestampedScoredNetwork.getUpdatedTimestampMillis() < evictionCutoff) {
+ iterator.remove();
+ }
+ });
+
return updateSpeed();
}
@@ -474,15 +498,15 @@
// set speed to the connected ScanResult if the AccessPoint is the active network
if (isActive() && mInfo != null) {
- ScoredNetwork score = mScoredNetworkCache.get(mInfo.getBSSID());
- if (score != null) {
+ TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(mInfo.getBSSID());
+ if (timedScore != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Set score using specific access point curve for connected AP: "
+ getSsidStr());
}
// TODO(b/63073866): Map using getLevel rather than specific rssi value so score
// doesn't change without a visible wifi bar change.
- int speed = score.calculateBadge(mInfo.getRssi());
+ int speed = timedScore.getScore().calculateBadge(mInfo.getRssi());
if (speed != Speed.NONE) {
mSpeed = speed;
}
@@ -509,8 +533,8 @@
int count = 0;
int totalSpeed = 0;
- for (ScoredNetwork score : mScoredNetworkCache.values()) {
- int speed = score.calculateBadge(mRssi);
+ for (TimestampedScoredNetwork timedScore : mScoredNetworkCache.values()) {
+ int speed = timedScore.getScore().calculateBadge(mRssi);
if (speed != Speed.NONE) {
count++;
totalSpeed += speed;
@@ -555,7 +579,7 @@
for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
ScanResult result = iter.next();
// result timestamp is in microseconds
- if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MS) {
+ if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) {
iter.remove();
}
}
@@ -1004,13 +1028,13 @@
}
@Speed private int getSpecificApSpeed(ScanResult result) {
- ScoredNetwork score = mScoredNetworkCache.get(result.BSSID);
- if (score == null) {
+ TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
+ if (timedScore == null) {
return Speed.NONE;
}
// For debugging purposes we may want to use mRssi rather than result.level as the average
// speed wil be determined by mRssi
- return score.calculateBadge(result.level);
+ return timedScore.getScore().calculateBadge(result.level);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 93bf3c7..3dec1d3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -24,6 +24,7 @@
import android.net.wifi.WifiInfo;
import android.os.Bundle;
import android.support.annotation.Keep;
+
import com.android.settingslib.wifi.AccessPoint.Speed;
import java.util.ArrayList;
@@ -58,6 +59,7 @@
Context mContext;
private ArrayList<ScanResult> mScanResultCache;
+ private ArrayList<TimestampedScoredNetwork> mScoredNetworkCache;
@Keep
public TestAccessPointBuilder(Context context) {
@@ -85,6 +87,9 @@
if (mScanResultCache != null) {
bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, mScanResultCache);
}
+ if (mScoredNetworkCache != null) {
+ bundle.putParcelableArrayList(AccessPoint.KEY_SCOREDNETWORKCACHE, mScoredNetworkCache);
+ }
bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
@@ -238,4 +243,10 @@
mCarrierName = carrierName;
return this;
}
+
+ public TestAccessPointBuilder setScoredNetworkCache(
+ ArrayList<TimestampedScoredNetwork> scoredNetworkCache) {
+ mScoredNetworkCache = scoredNetworkCache;
+ return this;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TimestampedScoredNetwork.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TimestampedScoredNetwork.java
new file mode 100644
index 0000000..cb15a79
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TimestampedScoredNetwork.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.settingslib.wifi;
+
+import android.net.ScoredNetwork;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data encapsulation object to associate a time with a {@link ScoredNetwork}
+ */
+class TimestampedScoredNetwork implements Parcelable {
+ private ScoredNetwork mScore;
+ private long mUpdatedTimestampMillis;
+
+ TimestampedScoredNetwork(ScoredNetwork score, long updatedTimestampMillis) {
+ mScore = score;
+ mUpdatedTimestampMillis = updatedTimestampMillis;
+ }
+
+ protected TimestampedScoredNetwork(Parcel in) {
+ mScore = ScoredNetwork.CREATOR.createFromParcel(in);
+ mUpdatedTimestampMillis = in.readLong();
+ }
+
+ public void update(ScoredNetwork score, long updatedTimestampMillis) {
+ mScore = score;
+ mUpdatedTimestampMillis = updatedTimestampMillis;
+ }
+
+ public ScoredNetwork getScore() {
+ return mScore;
+ }
+
+ public long getUpdatedTimestampMillis() {
+ return mUpdatedTimestampMillis;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mScore, flags);
+ dest.writeLong(mUpdatedTimestampMillis);
+ }
+
+ public static final Creator<TimestampedScoredNetwork> CREATOR =
+ new Creator<TimestampedScoredNetwork>() {
+ @Override
+ public TimestampedScoredNetwork createFromParcel(Parcel in) {
+ return new TimestampedScoredNetwork(in);
+ }
+
+ @Override
+ public TimestampedScoredNetwork[] newArray(int size) {
+ return new TimestampedScoredNetwork[size];
+ }
+ };
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 083d0c5..6f1b25f 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -21,8 +21,6 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -50,8 +48,8 @@
import android.text.style.TtsSpan;
import com.android.settingslib.R;
-
import com.android.settingslib.wifi.AccessPoint.Speed;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,17 +57,22 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AccessPointTest {
- private static final String TEST_SSID = "test_ssid";
+ private static final String TEST_SSID = "\"test_ssid\"";
private static final int NUM_SCAN_RESULTS = 5;
private static final ArrayList<ScanResult> SCAN_RESULTS = buildScanResultCache();
+ private static final RssiCurve FAST_BADGE_CURVE =
+ new RssiCurve(-150, 10, new byte[]{Speed.FAST});
+ public static final String TEST_BSSID = "00:00:00:00:00:00";
+
private Context mContext;
@Mock private RssiCurve mockBadgeCurve;
@Mock private WifiNetworkScoreCache mockWifiNetworkScoreCache;
@@ -602,8 +605,8 @@
Bundle attr1 = new Bundle();
attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, badgeCurve);
return new ScoredNetwork(
- new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
- mockBadgeCurve,
+ new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)),
+ badgeCurve,
false /* meteredHint */,
attr1);
@@ -632,6 +635,18 @@
return configuration;
}
+ private AccessPoint createApWithFastTimestampedScoredNetworkCache(
+ long elapsedTimeMillis) {
+ TimestampedScoredNetwork recentScore = new TimestampedScoredNetwork(
+ buildScoredNetworkWithGivenBadgeCurve(FAST_BADGE_CURVE),
+ elapsedTimeMillis);
+ return new TestAccessPointBuilder(mContext)
+ .setSsid(TEST_SSID)
+ .setScoredNetworkCache(
+ new ArrayList<>(Arrays.asList(recentScore)))
+ .build();
+ }
+
/**
* Assert that the first AccessPoint appears before the second AccessPoint
* once sorting has been completed.
@@ -964,4 +979,111 @@
assertThat(ap.getSpeed()).isEqualTo(fallbackSpeed);
}
+
+ @Test
+ public void testScoredNetworkCacheBundling() {
+ long timeMillis = SystemClock.elapsedRealtime();
+ AccessPoint ap = createApWithFastTimestampedScoredNetworkCache(timeMillis);
+ Bundle bundle = new Bundle();
+ ap.saveWifiState(bundle);
+
+ ArrayList<TimestampedScoredNetwork> list =
+ bundle.getParcelableArrayList(AccessPoint.KEY_SCOREDNETWORKCACHE);
+ assertThat(list).hasSize(1);
+ assertThat(list.get(0).getUpdatedTimestampMillis()).isEqualTo(timeMillis);
+
+ RssiCurve curve = list.get(0).getScore().attributes.getParcelable(
+ ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE);
+ assertThat(curve).isEqualTo(FAST_BADGE_CURVE);
+ }
+
+ @Test
+ public void testRecentNetworkScoresAreUsedForSpeedLabelGeneration() {
+ AccessPoint ap =
+ createApWithFastTimestampedScoredNetworkCache(SystemClock.elapsedRealtime());
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(Speed.FAST);
+ }
+
+ @Test
+ public void testNetworkScoresAreUsedForSpeedLabelGenerationWhenWithinAgeRange() {
+ long withinRangeTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS - 10000);
+ AccessPoint ap =
+ createApWithFastTimestampedScoredNetworkCache(withinRangeTimeMillis);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(Speed.FAST);
+ }
+
+ @Test
+ public void testOldNetworkScoresAreNotUsedForSpeedLabelGeneration() {
+ long tooOldTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS + 1);
+ AccessPoint ap =
+ createApWithFastTimestampedScoredNetworkCache(tooOldTimeMillis);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(Speed.NONE);
+ }
+
+ @Test
+ public void testUpdateScoresRefreshesScoredNetworkCacheTimestamps () {
+ long tooOldTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS + 1);
+
+ ScoredNetwork scoredNetwork = buildScoredNetworkWithGivenBadgeCurve(FAST_BADGE_CURVE);
+ TimestampedScoredNetwork recentScore = new TimestampedScoredNetwork(
+ scoredNetwork,
+ tooOldTimeMillis);
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setSsid(TEST_SSID)
+ .setBssid(TEST_BSSID)
+ .setActive(true)
+ .setScoredNetworkCache(
+ new ArrayList(Arrays.asList(recentScore)))
+ .setScanResultCache(SCAN_RESULTS)
+ .build();
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(scoredNetwork);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ // Fast should still be returned since cache was updated with recent time
+ assertThat(ap.getSpeed()).isEqualTo(Speed.FAST);
+ }
+
+ @Test
+ public void testUpdateScoresRefreshesScoredNetworkCacheWithNewSpeed () {
+ long tooOldTimeMillis =
+ SystemClock.elapsedRealtime() - (AccessPoint.MAX_CACHED_SCORE_AGE_MILLIS + 1);
+
+ ScoredNetwork scoredNetwork = buildScoredNetworkWithGivenBadgeCurve(FAST_BADGE_CURVE);
+ TimestampedScoredNetwork recentScore = new TimestampedScoredNetwork(
+ scoredNetwork,
+ tooOldTimeMillis);
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setSsid(TEST_SSID)
+ .setBssid(TEST_BSSID)
+ .setActive(true)
+ .setScoredNetworkCache(
+ new ArrayList(Arrays.asList(recentScore)))
+ .setScanResultCache(SCAN_RESULTS)
+ .build();
+
+ int newSpeed = Speed.MODERATE;
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) newSpeed);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ // Fast should still be returned since cache was updated with recent time
+ assertThat(ap.getSpeed()).isEqualTo(newSpeed);
+ }
}