| /* |
| * Copyright (C) 2021 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.vcn; |
| |
| import static com.android.server.vcn.VcnTestUtils.setupSystemService; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.LinkProperties; |
| import android.net.Network; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkRequest; |
| import android.net.TelephonyNetworkSpecifier; |
| import android.os.ParcelUuid; |
| import android.os.test.TestLooper; |
| import android.telephony.SubscriptionInfo; |
| import android.util.ArraySet; |
| |
| import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; |
| import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback; |
| import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback; |
| import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; |
| import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| public class UnderlyingNetworkTrackerTest { |
| private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); |
| private static final int INITIAL_SUB_ID_1 = 1; |
| private static final int INITIAL_SUB_ID_2 = 2; |
| private static final int UPDATED_SUB_ID = 3; |
| |
| private static final Set<Integer> INITIAL_SUB_IDS = |
| new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2)); |
| private static final Set<Integer> UPDATED_SUB_IDS = |
| new ArraySet<>(Arrays.asList(UPDATED_SUB_ID)); |
| |
| private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES = |
| new NetworkCapabilities.Builder() |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) |
| .build(); |
| private static final NetworkCapabilities SUSPENDED_NETWORK_CAPABILITIES = |
| new NetworkCapabilities.Builder(INITIAL_NETWORK_CAPABILITIES) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) |
| .build(); |
| private static final NetworkCapabilities UPDATED_NETWORK_CAPABILITIES = |
| new NetworkCapabilities.Builder() |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) |
| .build(); |
| |
| private static final LinkProperties INITIAL_LINK_PROPERTIES = |
| getLinkPropertiesWithName("initial_iface"); |
| private static final LinkProperties UPDATED_LINK_PROPERTIES = |
| getLinkPropertiesWithName("updated_iface"); |
| |
| @Mock private Context mContext; |
| @Mock private VcnNetworkProvider mVcnNetworkProvider; |
| @Mock private ConnectivityManager mConnectivityManager; |
| @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; |
| @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb; |
| @Mock private Network mNetwork; |
| |
| @Captor private ArgumentCaptor<RouteSelectionCallback> mRouteSelectionCallbackCaptor; |
| |
| private TestLooper mTestLooper; |
| private VcnContext mVcnContext; |
| private UnderlyingNetworkTracker mUnderlyingNetworkTracker; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| |
| mTestLooper = new TestLooper(); |
| mVcnContext = |
| spy( |
| new VcnContext( |
| mContext, |
| mTestLooper.getLooper(), |
| mVcnNetworkProvider, |
| false /* isInTestMode */)); |
| resetVcnContext(); |
| |
| setupSystemService( |
| mContext, |
| mConnectivityManager, |
| Context.CONNECTIVITY_SERVICE, |
| ConnectivityManager.class); |
| |
| when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS); |
| |
| mUnderlyingNetworkTracker = |
| new UnderlyingNetworkTracker( |
| mVcnContext, |
| SUB_GROUP, |
| mSubscriptionSnapshot, |
| Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET), |
| mNetworkTrackerCb); |
| } |
| |
| private void resetVcnContext() { |
| reset(mVcnContext); |
| doNothing().when(mVcnContext).ensureRunningOnLooperThread(); |
| } |
| |
| private static LinkProperties getLinkPropertiesWithName(String iface) { |
| LinkProperties linkProperties = new LinkProperties(); |
| linkProperties.setInterfaceName(iface); |
| return linkProperties; |
| } |
| |
| private SubscriptionInfo getSubscriptionInfoForSubId(int subId) { |
| SubscriptionInfo subInfo = mock(SubscriptionInfo.class); |
| when(subInfo.getSubscriptionId()).thenReturn(subId); |
| return subInfo; |
| } |
| |
| @Test |
| public void testNetworkCallbacksRegisteredOnStartup() { |
| verifyNetworkRequestsRegistered(INITIAL_SUB_IDS); |
| } |
| |
| @Test |
| public void testNetworkCallbacksRegisteredOnStartupForTestMode() { |
| resetVcnContext(); |
| when(mVcnContext.isInTestMode()).thenReturn(true); |
| reset(mConnectivityManager); |
| |
| mUnderlyingNetworkTracker = |
| new UnderlyingNetworkTracker( |
| mVcnContext, |
| SUB_GROUP, |
| mSubscriptionSnapshot, |
| Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET), |
| mNetworkTrackerCb); |
| |
| verifyNetworkRequestsRegistered(INITIAL_SUB_IDS, true /* expectTestMode */); |
| } |
| |
| private void verifyNetworkRequestsRegistered(Set<Integer> expectedSubIds) { |
| verifyNetworkRequestsRegistered(expectedSubIds, false /* expectTestMode */); |
| } |
| |
| private void verifyNetworkRequestsRegistered( |
| Set<Integer> expectedSubIds, boolean expectTestMode) { |
| verify(mConnectivityManager) |
| .requestBackgroundNetwork( |
| eq(getWifiRequest(expectedSubIds)), |
| any(NetworkBringupCallback.class), |
| any()); |
| for (final int subId : expectedSubIds) { |
| verify(mConnectivityManager) |
| .requestBackgroundNetwork( |
| eq(getCellRequestForSubId(subId)), |
| any(NetworkBringupCallback.class), any()); |
| } |
| |
| final NetworkRequest expectedRouteSelectionRequest = |
| expectTestMode |
| ? getTestNetworkRequest(expectedSubIds) |
| : getRouteSelectionRequest(expectedSubIds); |
| |
| verify(mConnectivityManager) |
| .requestBackgroundNetwork( |
| eq(expectedRouteSelectionRequest), |
| any(RouteSelectionCallback.class), |
| any()); |
| } |
| |
| @Test |
| public void testUpdateSubscriptionSnapshot() { |
| // Verify initial cell background requests filed |
| verifyNetworkRequestsRegistered(INITIAL_SUB_IDS); |
| |
| TelephonySubscriptionSnapshot subscriptionUpdate = |
| mock(TelephonySubscriptionSnapshot.class); |
| when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS); |
| |
| mUnderlyingNetworkTracker.updateSubscriptionSnapshot(subscriptionUpdate); |
| |
| // verify that initially-filed bringup requests are unregistered (cell + wifi) |
| verify(mConnectivityManager, times(INITIAL_SUB_IDS.size() + 1)) |
| .unregisterNetworkCallback(any(NetworkBringupCallback.class)); |
| verify(mConnectivityManager).unregisterNetworkCallback(any(RouteSelectionCallback.class)); |
| verifyNetworkRequestsRegistered(UPDATED_SUB_IDS); |
| } |
| |
| private NetworkRequest getWifiRequest(Set<Integer> netCapsSubIds) { |
| return getExpectedRequestBase() |
| .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) |
| .setSubscriptionIds(netCapsSubIds) |
| .build(); |
| } |
| |
| private NetworkRequest getCellRequestForSubId(int subId) { |
| return getExpectedRequestBase() |
| .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) |
| .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) |
| .build(); |
| } |
| |
| private NetworkRequest getRouteSelectionRequest(Set<Integer> netCapsSubIds) { |
| return getExpectedRequestBase().setSubscriptionIds(netCapsSubIds).build(); |
| } |
| |
| private NetworkRequest getTestNetworkRequest(Set<Integer> netCapsSubIds) { |
| return getExpectedRequestBase() |
| .addTransportType(NetworkCapabilities.TRANSPORT_TEST) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) |
| .setSubscriptionIds(netCapsSubIds) |
| .build(); |
| } |
| |
| private NetworkRequest.Builder getExpectedRequestBase() { |
| final NetworkRequest.Builder builder = |
| new NetworkRequest.Builder() |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); |
| |
| return builder; |
| } |
| |
| @Test |
| public void testTeardown() { |
| mUnderlyingNetworkTracker.teardown(); |
| |
| // Expect 3 NetworkBringupCallbacks to be unregistered: 1 for WiFi and 2 for Cellular (1x |
| // for each subId) |
| verify(mConnectivityManager, times(3)) |
| .unregisterNetworkCallback(any(NetworkBringupCallback.class)); |
| verify(mConnectivityManager).unregisterNetworkCallback(any(RouteSelectionCallback.class)); |
| } |
| |
| @Test |
| public void testUnderlyingNetworkRecordEquals() { |
| UnderlyingNetworkRecord recordA = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| INITIAL_NETWORK_CAPABILITIES, |
| INITIAL_LINK_PROPERTIES, |
| false /* isBlocked */); |
| UnderlyingNetworkRecord recordB = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| INITIAL_NETWORK_CAPABILITIES, |
| INITIAL_LINK_PROPERTIES, |
| false /* isBlocked */); |
| UnderlyingNetworkRecord recordC = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| UPDATED_NETWORK_CAPABILITIES, |
| UPDATED_LINK_PROPERTIES, |
| false /* isBlocked */); |
| |
| assertEquals(recordA, recordB); |
| assertNotEquals(recordA, recordC); |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackNotifiedForNetworkChange() { |
| verifyRegistrationOnAvailableAndGetCallback(); |
| } |
| |
| private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback() { |
| return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES); |
| } |
| |
| private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback( |
| NetworkCapabilities networkCapabilities) { |
| verify(mConnectivityManager) |
| .requestBackgroundNetwork( |
| eq(getRouteSelectionRequest(INITIAL_SUB_IDS)), |
| mRouteSelectionCallbackCaptor.capture(), |
| any()); |
| |
| RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue(); |
| cb.onAvailable(mNetwork); |
| cb.onCapabilitiesChanged(mNetwork, networkCapabilities); |
| cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES); |
| cb.onBlockedStatusChanged(mNetwork, false /* isFalse */); |
| |
| UnderlyingNetworkRecord expectedRecord = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| networkCapabilities, |
| INITIAL_LINK_PROPERTIES, |
| false /* isBlocked */); |
| verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| return cb; |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() { |
| RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); |
| |
| cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); |
| |
| UnderlyingNetworkRecord expectedRecord = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| UPDATED_NETWORK_CAPABILITIES, |
| INITIAL_LINK_PROPERTIES, |
| false /* isBlocked */); |
| verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() { |
| RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); |
| |
| cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES); |
| |
| UnderlyingNetworkRecord expectedRecord = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| INITIAL_NETWORK_CAPABILITIES, |
| UPDATED_LINK_PROPERTIES, |
| false /* isBlocked */); |
| verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackNotifiedForNetworkSuspended() { |
| RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); |
| |
| cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); |
| |
| UnderlyingNetworkRecord expectedRecord = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| SUSPENDED_NETWORK_CAPABILITIES, |
| INITIAL_LINK_PROPERTIES, |
| false /* isBlocked */); |
| verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't |
| // change. |
| cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); |
| verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackNotifiedForNetworkResumed() { |
| RouteSelectionCallback cb = |
| verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES); |
| |
| cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); |
| |
| UnderlyingNetworkRecord expectedRecord = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| INITIAL_NETWORK_CAPABILITIES, |
| INITIAL_LINK_PROPERTIES, |
| false /* isBlocked */); |
| verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't |
| // change. |
| cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); |
| verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackNotifiedForBlocked() { |
| RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); |
| |
| cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */); |
| |
| UnderlyingNetworkRecord expectedRecord = |
| new UnderlyingNetworkRecord( |
| mNetwork, |
| INITIAL_NETWORK_CAPABILITIES, |
| INITIAL_LINK_PROPERTIES, |
| true /* isBlocked */); |
| verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackNotifiedForNetworkLoss() { |
| RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); |
| |
| cb.onLost(mNetwork); |
| |
| verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null); |
| } |
| |
| @Test |
| public void testRecordTrackerCallbackIgnoresDuplicateRecord() { |
| RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); |
| |
| cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); |
| |
| // Verify no more calls to the UnderlyingNetworkTrackerCallback when the |
| // UnderlyingNetworkRecord does not actually change |
| verifyNoMoreInteractions(mNetworkTrackerCb); |
| } |
| } |