From ba242734015ec89d434289c0905d0cc977c0a414 Mon Sep 17 00:00:00 2001 From: Jeremy Joslin Date: Tue, 24 Jan 2017 17:16:42 -0800 Subject: [PATCH] Implemented network score cache filtering. NetworkScoreCaches registered with a filter type will now only receive updates that match their filter criteria. Test: runtest frameworks-services -c com.android.server.NetworkScoreServiceTest Bug: 34127291 Change-Id: I3d5e6e57b6682707b0734259a2530658ad75dbb7 --- core/java/android/net/NetworkKey.java | 25 ++ .../src/android/net/NetworkKeyTest.java | 75 +++++ .../android/server/NetworkScoreService.java | 261 ++++++++++++++++-- .../server/NetworkScoreServiceTest.java | 209 +++++++++++++- 4 files changed, 549 insertions(+), 21 deletions(-) create mode 100644 core/tests/coretests/src/android/net/NetworkKeyTest.java diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java index 1a128e05ce6ed..e5f0bf000f644 100644 --- a/core/java/android/net/NetworkKey.java +++ b/core/java/android/net/NetworkKey.java @@ -16,10 +16,14 @@ package android.net; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.wifi.ScanResult; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiSsid; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.util.Objects; @@ -64,6 +68,27 @@ public class NetworkKey implements Parcelable { '"' + result.wifiSsid.toString() + '"', result.BSSID)); } + /** + * Constructs a new NetworkKey for the given {@link WifiInfo}. + * + * @param wifiInfo the {@link WifiInfo} to create a {@link NetworkKey} for. + * @return A new {@link NetworkKey} instance or null if the given {@link WifiInfo} + * instance doesn't represent a connected WiFi network. + * @hide + */ + @Nullable + public static NetworkKey createFromWifiInfo(@Nullable WifiInfo wifiInfo) { + if (wifiInfo != null) { + final String ssid = wifiInfo.getSSID(); + final String bssid = wifiInfo.getBSSID(); + if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE) + && !TextUtils.isEmpty(bssid)) { + return new NetworkKey(new WifiKey(ssid, bssid)); + } + } + return null; + } + /** * Construct a new {@link NetworkKey} for a Wi-Fi network. * @param wifiKey the {@link WifiKey} identifying this Wi-Fi network. diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java new file mode 100644 index 0000000000000..1afe9da2bfe60 --- /dev/null +++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java @@ -0,0 +1,75 @@ +package android.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; + +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiSsid; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class NetworkKeyTest { + private static final String VALID_SSID = "\"ssid1\""; + private static final String VALID_BSSID = "00:00:00:00:00:00"; + @Mock private WifiInfo mWifiInfo; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void createFromWifi_nullInput() throws Exception { + assertNull(NetworkKey.createFromWifiInfo(null)); + } + + @Test + public void createFromWifi_nullSsid() throws Exception { + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_emptySsid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(""); + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_noneSsid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE); + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_nullBssid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_emptyBssid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); + when(mWifiInfo.getBSSID()).thenReturn(""); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_validWifiInfo() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + + NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID)); + final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo); + assertEquals(expected, actual); + } +} diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index e8ecc3e5f44a8..dab4dfb41dec8 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -41,6 +41,10 @@ import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; import android.net.Uri; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiScanner; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -76,7 +80,9 @@ import java.util.Map; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; /** * Backing service for {@link android.net.NetworkScoreManager}. @@ -391,6 +397,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0; } + if (isEmpty) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "No scorer registered for type " + entry.getKey() @@ -399,18 +406,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { continue; } - sendCallback(new Consumer() { - @Override - public void accept(INetworkScoreCache networkScoreCache) { - try { - networkScoreCache.updateScores(entry.getValue()); - } catch (RemoteException e) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e); - } - } - } - }, Collections.singleton(callbackList)); + final BiConsumer consumer = + new FilteringCacheUpdatingConsumer(mContext, entry.getValue(), + entry.getKey()); + sendCacheUpdateCallback(consumer, Collections.singleton(callbackList)); } return true; @@ -419,6 +418,229 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } + /** + * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork} + * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the + * accepted {@link INetworkScoreCache} implementation. + */ + @VisibleForTesting + public static class FilteringCacheUpdatingConsumer + implements BiConsumer { + private final Context mContext; + private final List mScoredNetworkList; + private final int mNetworkType; + // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters. + private Function, List> mCurrentNetworkFilter; + private Function, List> mScanResultsFilter; + + public FilteringCacheUpdatingConsumer(Context context, + List scoredNetworkList, int networkType) { + this(context, scoredNetworkList, networkType, null, null); + } + + @VisibleForTesting + public FilteringCacheUpdatingConsumer(Context context, + List scoredNetworkList, int networkType, + Function, List> currentNetworkFilter, + Function, List> scanResultsFilter) { + mContext = context; + mScoredNetworkList = scoredNetworkList; + mNetworkType = networkType; + mCurrentNetworkFilter = currentNetworkFilter; + mScanResultsFilter = scanResultsFilter; + } + + @Override + public void accept(INetworkScoreCache networkScoreCache, Object cookie) { + int filterType = NetworkScoreManager.CACHE_FILTER_NONE; + if (cookie instanceof Integer) { + filterType = (Integer) cookie; + } + + try { + final List filteredNetworkList = + filterScores(mScoredNetworkList, filterType); + if (!filteredNetworkList.isEmpty()) { + networkScoreCache.updateScores( + Collections.unmodifiableList(filteredNetworkList)); + } + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Unable to update scores of type " + mNetworkType, e); + } + } + } + + /** + * Applies the appropriate filter and returns the filtered results. + */ + private List filterScores(List scoredNetworkList, + int filterType) { + switch (filterType) { + case NetworkScoreManager.CACHE_FILTER_NONE: + return scoredNetworkList; + + case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK: + if (mCurrentNetworkFilter == null) { + mCurrentNetworkFilter = + new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext)); + } + return mCurrentNetworkFilter.apply(scoredNetworkList); + + case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS: + if (mScanResultsFilter == null) { + mScanResultsFilter = new ScanResultsScoreCacheFilter( + new ScanResultsSupplier(mContext)); + } + return mScanResultsFilter.apply(scoredNetworkList); + + default: + Log.w(TAG, "Unknown filter type: " + filterType); + return scoredNetworkList; + } + } + } + + /** + * Helper class that improves the testability of the cache filter Functions. + */ + private static class WifiInfoSupplier implements Supplier { + private final Context mContext; + + WifiInfoSupplier(Context context) { + mContext = context; + } + + @Override + public WifiInfo get() { + WifiManager wifiManager = mContext.getSystemService(WifiManager.class); + if (wifiManager != null) { + return wifiManager.getConnectionInfo(); + } + Log.w(TAG, "WifiManager is null, failed to return the WifiInfo."); + return null; + } + } + + /** + * Helper class that improves the testability of the cache filter Functions. + */ + private static class ScanResultsSupplier implements Supplier> { + private final Context mContext; + + ScanResultsSupplier(Context context) { + mContext = context; + } + + @Override + public List get() { + WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class); + if (wifiScanner != null) { + return wifiScanner.getSingleScanResults(); + } + Log.w(TAG, "WifiScanner is null, failed to return scan results."); + return Collections.emptyList(); + } + } + + /** + * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the + * {@link ScoredNetwork} associated with the current network. If no network is connected the + * returned list will be empty. + *

+ * Note: this filter performs some internal caching for consistency and performance. The + * current network is determined at construction time and never changed. Also, the + * last filtered list is saved so if the same input is provided multiple times in a row + * the computation is only done once. + */ + @VisibleForTesting + public static class CurrentNetworkScoreCacheFilter + implements Function, List> { + private final NetworkKey mCurrentNetwork; + private Pair, Integer> mCache; + + CurrentNetworkScoreCacheFilter(Supplier wifiInfoSupplier) { + mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get()); + } + + @Override + public List apply(List scoredNetworks) { + if (mCurrentNetwork == null || scoredNetworks.isEmpty()) { + return Collections.emptyList(); + } + + final int inputListHash = scoredNetworks.hashCode(); + if (mCache == null || mCache.second != inputListHash) { + ScoredNetwork currentScore = null; + for (int i = 0; i < scoredNetworks.size(); i++) { + final ScoredNetwork scoredNetwork = scoredNetworks.get(i); + if (scoredNetwork.networkKey.equals(mCurrentNetwork)) { + currentScore = scoredNetwork; + break; + } + } + + if (currentScore == null) { + mCache = Pair.create(Collections.emptyList(), inputListHash); + } else { + mCache = Pair.create(Collections.singletonList(currentScore), inputListHash); + } + } + + return mCache.first; + } + } + + /** + * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the + * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s. + * If there are no {@link ScanResult}s the returned list will be empty. + *

+ * Note: this filter performs some internal caching for consistency and performance. The + * current set of ScanResults is determined at construction time and never changed. + * Also, the last filtered list is saved so if the same input is provided multiple + * times in a row the computation is only done once. + */ + @VisibleForTesting + public static class ScanResultsScoreCacheFilter + implements Function, List> { + private final List mScanResultKeys; + private Pair, Integer> mCache; + + ScanResultsScoreCacheFilter(Supplier> resultsSupplier) { + mScanResultKeys = new ArrayList<>(); + List scanResults = resultsSupplier.get(); + for (int i = 0; i < scanResults.size(); i++) { + ScanResult scanResult = scanResults.get(i); + mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult)); + } + } + + @Override + public List apply(List scoredNetworks) { + if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) { + return Collections.emptyList(); + } + + final int inputListHash = scoredNetworks.hashCode(); + if (mCache == null || mCache.second != inputListHash) { + List filteredScores = new ArrayList<>(); + for (int i = 0; i < scoredNetworks.size(); i++) { + final ScoredNetwork scoredNetwork = scoredNetworks.get(i); + for (int j = 0; j < mScanResultKeys.size(); j++) { + final NetworkKey scanResultKey = mScanResultKeys.get(j); + if (scanResultKey.equals(scoredNetwork.networkKey)) { + filteredScores.add(scoredNetwork); + } + } + } + mCache = Pair.create(filteredScores, inputListHash); + } + + return mCache.first; + } + } + private boolean isCallerSystemUid() { // REQUEST_NETWORK_SCORES is a signature only permission. return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) == @@ -499,9 +721,9 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** Clear scores. Callers are responsible for checking permissions as appropriate. */ private void clearInternal() { - sendCallback(new Consumer() { + sendCacheUpdateCallback(new BiConsumer() { @Override - public void accept(INetworkScoreCache networkScoreCache) { + public void accept(INetworkScoreCache networkScoreCache, Object cookie) { try { networkScoreCache.clearScores(); } catch (RemoteException e) { @@ -675,9 +897,9 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } writer.println("Current scorer: " + currentScorer.packageName); - sendCallback(new Consumer() { + sendCacheUpdateCallback(new BiConsumer() { @Override - public void accept(INetworkScoreCache networkScoreCache) { + public void accept(INetworkScoreCache networkScoreCache, Object cookie) { try { TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args); } catch (IOException | RemoteException e) { @@ -708,14 +930,15 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } - private void sendCallback(Consumer consumer, + private void sendCacheUpdateCallback(BiConsumer consumer, Collection> remoteCallbackLists) { for (RemoteCallbackList callbackList : remoteCallbackLists) { synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList final int count = callbackList.beginBroadcast(); try { for (int i = 0; i < count; i++) { - consumer.accept(callbackList.getBroadcastItem(i)); + consumer.accept(callbackList.getBroadcastItem(i), + callbackList.getRegisteredCallbackCookie(i)); } } finally { callbackList.finishBroadcast(); diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 43c895754890a..4ca29cdac02b8 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -63,7 +63,10 @@ import android.net.RecommendationResult; import android.net.ScoredNetwork; import android.net.Uri; import android.net.WifiKey; +import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiSsid; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -82,6 +85,8 @@ import android.support.test.runner.AndroidJUnit4; import com.android.server.devicepolicy.MockUtils; +import com.google.android.collect.Lists; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -96,9 +101,12 @@ import org.mockito.stubbing.Answer; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Function; /** * Tests for {@link NetworkScoreService}. @@ -106,19 +114,27 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @MediumTest public class NetworkScoreServiceTest { + private static final String SSID = "ssid"; + private static final String SSID_2 = "ssid_2"; + private static final String SSID_3 = "ssid_3"; private static final ScoredNetwork SCORED_NETWORK = - new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")), + new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID), "00:00:00:00:00:00")), + null /* rssiCurve*/); + private static final ScoredNetwork SCORED_NETWORK_2 = + new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")), null /* rssiCurve*/); private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass"); - @Mock private PackageManager mPackageManager; @Mock private NetworkScorerAppManager mNetworkScorerAppManager; @Mock private Context mContext; @Mock private Resources mResources; @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2; @Mock private IBinder mIBinder, mIBinder2; @Mock private INetworkRecommendationProvider mRecommendationProvider; + @Mock private Function, List> mCurrentNetworkFilter; + @Mock private Function, List> mScanResultsFilter; + @Mock private WifiInfo mWifiInfo; @Captor private ArgumentCaptor> mScoredNetworkCaptor; private ContentResolver mContentResolver; @@ -127,6 +143,11 @@ public class NetworkScoreServiceTest { private RemoteCallback mRemoteCallback; private OnResultListener mOnResultListener; private HandlerThread mHandlerThread; + private List mScanResults; + + private static String quote(String str) { + return String.format("\"%s\"", str); + } @Before public void setUp() throws Exception { @@ -136,6 +157,8 @@ public class NetworkScoreServiceTest { mContentResolver = InstrumentationRegistry.getContext().getContentResolver(); when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getResources()).thenReturn(mResources); + when(mWifiInfo.getSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.ssid); + when(mWifiInfo.getBSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.bssid); mHandlerThread = new HandlerThread("NetworkScoreServiceTest"); mHandlerThread.start(); mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager, @@ -150,6 +173,21 @@ public class NetworkScoreServiceTest { Settings.Global.putLong(mContentResolver, Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L); mNetworkScoreService.refreshRecommendationRequestTimeoutMs(); + populateScanResults(); + } + + private void populateScanResults() { + mScanResults = new ArrayList<>(); + mScanResults.add(createScanResult(SSID, SCORED_NETWORK.networkKey.wifiKey.bssid)); + mScanResults.add(createScanResult(SSID_2, SCORED_NETWORK_2.networkKey.wifiKey.bssid)); + mScanResults.add(createScanResult(SSID_3, "10:10:00:00:10:10")); + } + + private ScanResult createScanResult(String ssid, String bssid) { + ScanResult result = new ScanResult(); + result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid); + result.BSSID = bssid; + return result; } @After @@ -622,6 +660,173 @@ public class NetworkScoreServiceTest { assertEquals(NEW_SCORER.packageName, mNetworkScoreService.getActiveScorerPackage()); } + @Test + public void testCacheUpdatingConsumer_nullFilter() throws Exception { + List scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer consumer = + new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), NetworkKey.TYPE_WIFI, + mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, null /*cookie*/); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_noneFilter() throws Exception { + List scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_unknownFilter() throws Exception { + List scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, -1 /*cookie*/); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_nonIntFilter() throws Exception { + List scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_emptyScoreList() throws Exception { + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + Collections.emptyList(), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + + verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_currentNetworkFilter() throws Exception { + List scoredNetworkList = + Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + List filteredList = new ArrayList<>(scoredNetworkList); + filteredList.remove(SCORED_NETWORK); + when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); + + verify(mNetworkScoreCache).updateScores(filteredList); + verifyZeroInteractions(mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_scanResultsFilter() throws Exception { + List scoredNetworkList = + Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + List filteredList = new ArrayList<>(scoredNetworkList); + filteredList.remove(SCORED_NETWORK); + when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); + + verify(mNetworkScoreCache).updateScores(filteredList); + verifyZeroInteractions(mCurrentNetworkFilter); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_nullWifiInfo() throws Exception { + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> null /*WifiInfo*/); + + List actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_scoreFiltered() throws Exception { + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + List expectedList = Collections.singletonList(SCORED_NETWORK); + assertEquals(expectedList, actualList); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_currentNetworkNotInList() throws Exception { + when(mWifiInfo.getSSID()).thenReturn("\"notInList\""); + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testScanResultsScoreCacheFilter_emptyScanResults() throws Exception { + NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter = + new NetworkScoreService.ScanResultsScoreCacheFilter(Collections::emptyList); + + List actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testScanResultsScoreCacheFilter_scoresFiltered() throws Exception { + NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter = + new NetworkScoreService.ScanResultsScoreCacheFilter(() -> mScanResults); + + ScoredNetwork unmatchedScore = + new ScoredNetwork(new NetworkKey(new WifiKey(quote("newSsid"), + "00:00:00:00:00:00")), null /* rssiCurve*/); + + List actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2, + unmatchedScore)); + + List expectedList = Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2); + assertEquals(expectedList, actualList); + } + // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService. private void injectProvider() { final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,