From 96a4f4b8de7cc383ce403d9106800d166445aad1 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Tue, 10 Dec 2019 22:16:53 +0900 Subject: [PATCH] [NS B09] Create NetworkRanker Bug: 113554781 Test: FrameworksNetTests Change-Id: Ia534247144f479fe896e1a6e05b906103cd10005 --- .../android/server/ConnectivityService.java | 21 ++--- .../server/connectivity/NetworkRanker.java | 50 +++++++++++ .../server/connectivity/NetworkRankerTest.kt | 84 +++++++++++++++++++ 3 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 services/core/java/com/android/server/connectivity/NetworkRanker.java create mode 100644 tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index feea30fe52..a06cc95bc8 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -194,6 +194,7 @@ import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; +import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Vpn; @@ -579,6 +580,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final ConnectivityDiagnosticsHandler mConnectivityDiagnosticsHandler; private final DnsManager mDnsManager; + private final NetworkRanker mNetworkRanker; private boolean mSystemReady; private Intent mInitialBroadcast; @@ -958,6 +960,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mMetricsLog = logger; mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST); + mNetworkRanker = new NetworkRanker(); NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder()); mNetworkRequests.put(mDefaultRequest, defaultNRI); mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI); @@ -6660,24 +6663,12 @@ public class ConnectivityService extends IConnectivityManager.Stub changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(nai, nai.isBackgroundNetwork())); } - Collections.sort(nais); for (final NetworkRequestInfo nri : mNetworkRequests.values()) { if (nri.request.isListen()) continue; - // Find the top scoring network satisfying this request. - NetworkAgentInfo bestNetwork = null; - for (final NetworkAgentInfo nai : nais) { - if (!nai.satisfies(nri.request)) continue; - bestNetwork = nai; - // As the nais are sorted by score, this is the top-scoring network that can - // satisfy this request. The best network for this request has been found, - // go process the next NRI - break; - } - // If no NAI satisfies this request, bestNetwork is still null. That's fine : it - // means no network can satisfy the request. If nri.mSatisfier is not null, it just - // means the network that used to satisfy the request stopped satisfying it. - if (nri.mSatisfier != bestNetwork) { + final NetworkAgentInfo bestNetwork = mNetworkRanker.getBestNetwork(nri.request, nais); + if (bestNetwork != nri.mSatisfier) { + // bestNetwork may be null if no network can satisfy this request. changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( nri, nri.mSatisfier, bestNetwork)); } diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java new file mode 100644 index 0000000000..d0aabf95d5 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 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.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.NetworkRequest; + +import java.util.Collection; + +/** + * A class that knows how to find the best network matching a request out of a list of networks. + */ +public class NetworkRanker { + public NetworkRanker() { } + + /** + * Find the best network satisfying this request among the list of passed networks. + */ + // Almost equivalent to Collections.max(nais), but allows returning null if no network + // satisfies the request. + @Nullable + public NetworkAgentInfo getBestNetwork(@NonNull final NetworkRequest request, + @NonNull final Collection nais) { + NetworkAgentInfo bestNetwork = null; + int bestScore = Integer.MIN_VALUE; + for (final NetworkAgentInfo nai : nais) { + if (!nai.satisfies(request)) continue; + if (nai.getCurrentScore() > bestScore) { + bestNetwork = nai; + bestScore = nai.getCurrentScore(); + } + } + return bestNetwork; + } +} diff --git a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt new file mode 100644 index 0000000000..86c91165f6 --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 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.connectivity + +import android.net.NetworkRequest +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import kotlin.test.assertEquals +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkRankerTest { + private val ranker = NetworkRanker() + + private fun makeNai(satisfy: Boolean, score: Int) = mock(NetworkAgentInfo::class.java).also { + doReturn(satisfy).`when`(it).satisfies(any()) + doReturn(score).`when`(it).currentScore + } + + @Test + fun testGetBestNetwork() { + val scores = listOf(20, 50, 90, 60, 23, 68) + val nais = scores.map { makeNai(true, it) } + val bestNetwork = nais[2] // The one with the top score + val someRequest = mock(NetworkRequest::class.java) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testIgnoreNonSatisfying() { + val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90), + makeNai(false, 60), makeNai(true, 23), makeNai(false, 68)) + val bestNetwork = nais[1] // Top score that's satisfying + val someRequest = mock(NetworkRequest::class.java) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testNoMatch() { + val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90)) + val someRequest = mock(NetworkRequest::class.java) + assertNull(ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testEmpty() { + val someRequest = mock(NetworkRequest::class.java) + assertNull(ranker.getBestNetwork(someRequest, emptyList())) + } + + // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST + // network satisfying the request if multiple of them have the same score. + @Test + fun testStable() { + val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), + makeNai(true, 30), makeNai(true, 30), makeNai(true, 30)) + val someRequest = mock(NetworkRequest::class.java) + assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1)) + + val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20), + makeNai(true, 50), makeNai(true, 50), makeNai(true, 40)) + assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2)) + } +}