From 3d20aa2cf8e3d3658216be4cc6a6299921ed5978 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Mon, 15 Nov 2021 15:43:02 -0800 Subject: Rename UnderlyingNetworkTracker to UnderlyingNetworkController Bug: 206044122 Test: atest FrameworksVcnTests, CtsVcnTestCases Change-Id: I6b4888f8252999446a29076937d9695278ff6a66 --- .../com/android/server/VcnManagementService.java | 2 +- .../server/vcn/UnderlyingNetworkTracker.java | 827 -------------------- .../android/server/vcn/VcnGatewayConnection.java | 48 +- .../UnderlyingNetworkController.java | 828 +++++++++++++++++++++ .../server/vcn/UnderlyingNetworkTrackerTest.java | 491 ------------ .../VcnGatewayConnectionConnectedStateTest.java | 8 +- .../VcnGatewayConnectionConnectingStateTest.java | 6 +- .../VcnGatewayConnectionDisconnectedStateTest.java | 4 +- .../VcnGatewayConnectionRetryTimeoutStateTest.java | 6 +- .../server/vcn/VcnGatewayConnectionTest.java | 10 +- .../server/vcn/VcnGatewayConnectionTestBase.java | 11 +- .../UnderlyingNetworkControllerTest.java | 491 ++++++++++++ 12 files changed, 1368 insertions(+), 1364 deletions(-) delete mode 100644 services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java create mode 100644 services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java delete mode 100644 tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java create mode 100644 tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index b068f86ff0ab..0c990ecfc827 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -141,7 +141,7 @@ import java.util.concurrent.TimeUnit; * | or its properties * v | * +-----------------------------------------------------------------------+ - * | UnderlyingNetworkTracker | + * | UnderlyingNetworkController | * | | * | Manages lifecycle of underlying physical networks, filing requests to | * | bring them up, and releasing them as they become no longer necessary | diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java deleted file mode 100644 index 7ddd1355a2d6..000000000000 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ /dev/null @@ -1,827 +0,0 @@ -/* - * 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.vcn; - -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; - -import static com.android.server.VcnManagementService.LOCAL_LOG; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.net.ConnectivityManager; -import android.net.ConnectivityManager.NetworkCallback; -import android.net.LinkProperties; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; -import android.net.TelephonyNetworkSpecifier; -import android.net.vcn.VcnManager; -import android.os.Handler; -import android.os.HandlerExecutor; -import android.os.ParcelUuid; -import android.os.PersistableBundle; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyCallback; -import android.telephony.TelephonyManager; -import android.util.ArrayMap; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.internal.util.IndentingPrintWriter; -import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; - -/** - * Tracks a set of Networks underpinning a VcnGatewayConnection. - * - *

A single UnderlyingNetworkTracker is built to serve a SINGLE VCN Gateway Connection, and MUST - * be torn down with the VcnGatewayConnection in order to ensure underlying networks are allowed to - * be reaped. - * - * @hide - */ -public class UnderlyingNetworkTracker { - @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName(); - - /** - * Minimum signal strength for a WiFi network to be eligible for switching to - * - *

A network that satisfies this is eligible to become the selected underlying network with - * no additional conditions - */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70; - - /** - * Minimum signal strength to continue using a WiFi network - * - *

A network that satisfies the conditions may ONLY continue to be used if it is already - * selected as the underlying network. A WiFi network satisfying this condition, but NOT the - * prospective-network RSSI threshold CANNOT be switched to. - */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74; - - /** Priority for any cellular network for which the subscription is listed as opportunistic */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0; - - /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_WIFI_IN_USE = 1; - - /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_WIFI_PROSPECTIVE = 2; - - /** Priority for any standard macro cellular network */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_MACRO_CELLULAR = 3; - - /** Priority for any other networks (including unvalidated, etc) */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_ANY = Integer.MAX_VALUE; - - private static final SparseArray PRIORITY_TO_STRING_MAP = new SparseArray<>(); - - static { - PRIORITY_TO_STRING_MAP.put( - PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY"); - } - - @NonNull private final VcnContext mVcnContext; - @NonNull private final ParcelUuid mSubscriptionGroup; - @NonNull private final UnderlyingNetworkTrackerCallback mCb; - @NonNull private final Dependencies mDeps; - @NonNull private final Handler mHandler; - @NonNull private final ConnectivityManager mConnectivityManager; - @NonNull private final TelephonyCallback mActiveDataSubIdListener = - new VcnActiveDataSubscriptionIdListener(); - - @NonNull private final List mCellBringupCallbacks = new ArrayList<>(); - @Nullable private NetworkCallback mWifiBringupCallback; - @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback; - @Nullable private NetworkCallback mWifiExitRssiThresholdCallback; - @Nullable private UnderlyingNetworkListener mRouteSelectionCallback; - - @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; - @Nullable private PersistableBundle mCarrierConfig; - private boolean mIsQuitting = false; - - @Nullable private UnderlyingNetworkRecord mCurrentRecord; - @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; - - public UnderlyingNetworkTracker( - @NonNull VcnContext vcnContext, - @NonNull ParcelUuid subscriptionGroup, - @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull UnderlyingNetworkTrackerCallback cb) { - this( - vcnContext, - subscriptionGroup, - snapshot, - cb, - new Dependencies()); - } - - private UnderlyingNetworkTracker( - @NonNull VcnContext vcnContext, - @NonNull ParcelUuid subscriptionGroup, - @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull UnderlyingNetworkTrackerCallback cb, - @NonNull Dependencies deps) { - mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); - mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); - mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); - mCb = Objects.requireNonNull(cb, "Missing cb"); - mDeps = Objects.requireNonNull(deps, "Missing deps"); - - mHandler = new Handler(mVcnContext.getLooper()); - - mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class); - mVcnContext - .getContext() - .getSystemService(TelephonyManager.class) - .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener); - - // TODO: Listen for changes in carrier config that affect this. - for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { - PersistableBundle config = - mVcnContext - .getContext() - .getSystemService(CarrierConfigManager.class) - .getConfigForSubId(subId); - - if (config != null) { - mCarrierConfig = config; - - // Attempt to use (any) non-opportunistic subscription. If this subscription is - // opportunistic, continue and try to find a non-opportunistic subscription, using - // the opportunistic ones as a last resort. - if (!isOpportunistic(mLastSnapshot, Collections.singleton(subId))) { - break; - } - } - } - - registerOrUpdateNetworkRequests(); - } - - private void registerOrUpdateNetworkRequests() { - NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback; - NetworkCallback oldWifiCallback = mWifiBringupCallback; - NetworkCallback oldWifiEntryRssiThresholdCallback = mWifiEntryRssiThresholdCallback; - NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback; - List oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks); - mCellBringupCallbacks.clear(); - - // Register new callbacks. Make-before-break; always register new callbacks before removal - // of old callbacks - if (!mIsQuitting) { - mRouteSelectionCallback = new UnderlyingNetworkListener(); - mConnectivityManager.registerNetworkCallback( - getRouteSelectionRequest(), mRouteSelectionCallback, mHandler); - - mWifiEntryRssiThresholdCallback = new NetworkBringupCallback(); - mConnectivityManager.registerNetworkCallback( - getWifiEntryRssiThresholdNetworkRequest(), - mWifiEntryRssiThresholdCallback, - mHandler); - - mWifiExitRssiThresholdCallback = new NetworkBringupCallback(); - mConnectivityManager.registerNetworkCallback( - getWifiExitRssiThresholdNetworkRequest(), - mWifiExitRssiThresholdCallback, - mHandler); - - mWifiBringupCallback = new NetworkBringupCallback(); - mConnectivityManager.requestBackgroundNetwork( - getWifiNetworkRequest(), mWifiBringupCallback, mHandler); - - for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { - final NetworkBringupCallback cb = new NetworkBringupCallback(); - mCellBringupCallbacks.add(cb); - - mConnectivityManager.requestBackgroundNetwork( - getCellNetworkRequestForSubId(subId), cb, mHandler); - } - } else { - mRouteSelectionCallback = null; - mWifiBringupCallback = null; - mWifiEntryRssiThresholdCallback = null; - mWifiExitRssiThresholdCallback = null; - // mCellBringupCallbacks already cleared above. - } - - // Unregister old callbacks (as necessary) - if (oldRouteSelectionCallback != null) { - mConnectivityManager.unregisterNetworkCallback(oldRouteSelectionCallback); - } - if (oldWifiCallback != null) { - mConnectivityManager.unregisterNetworkCallback(oldWifiCallback); - } - if (oldWifiEntryRssiThresholdCallback != null) { - mConnectivityManager.unregisterNetworkCallback(oldWifiEntryRssiThresholdCallback); - } - if (oldWifiExitRssiThresholdCallback != null) { - mConnectivityManager.unregisterNetworkCallback(oldWifiExitRssiThresholdCallback); - } - for (NetworkCallback cellBringupCallback : oldCellCallbacks) { - mConnectivityManager.unregisterNetworkCallback(cellBringupCallback); - } - } - - /** - * Builds the Route selection request - * - *

This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue - * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only - * carrier owned networks may be selected, as the request specifies only subIds in the VCN's - * subscription group, while the VCN networks are excluded by virtue of not having subIds set on - * the VCN-exposed networks. - * - *

If the VCN that this UnderlyingNetworkTracker belongs to is in test-mode, this will return - * a NetworkRequest that only matches Test Networks. - */ - private NetworkRequest getRouteSelectionRequest() { - if (mVcnContext.isInTestMode()) { - return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)); - } - - return getBaseNetworkRequestBuilder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) - .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) - .build(); - } - - /** - * Builds the WiFi bringup request - * - *

This request is built specifically to match only carrier-owned WiFi networks, but is also - * built to ONLY keep Carrier WiFi Networks alive (but never bring them up). This is a result of - * the WifiNetworkFactory not advertising a list of subIds, and therefore not accepting this - * request. As such, it will bind to a Carrier WiFi Network that has already been brought up, - * but will NEVER bring up a Carrier WiFi network itself. - */ - private NetworkRequest getWifiNetworkRequest() { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) - .build(); - } - - /** - * Builds the WiFi entry threshold signal strength request - * - *

This request ensures that WiFi reports the crossing of the wifi entry RSSI threshold. - * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a - * pace to effectively select a short-lived WiFi offload network. - */ - private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) - // Ensure wifi updates signal strengths when crossing this threshold. - .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig)) - .build(); - } - - /** - * Builds the WiFi exit threshold signal strength request - * - *

This request ensures that WiFi reports the crossing of the wifi exit RSSI threshold. - * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a - * pace to effectively select away from a failing WiFi network. - */ - private NetworkRequest getWifiExitRssiThresholdNetworkRequest() { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) - // Ensure wifi updates signal strengths when crossing this threshold. - .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig)) - .build(); - } - - /** - * Builds a Cellular bringup request for a given subId - * - *

This request is filed in order to ensure that the Telephony stack always has a - * NetworkRequest to bring up a VCN underlying cellular network. It is required in order to - * ensure that even when a VCN (appears as Cellular) satisfies the default request, Telephony - * will bring up additional underlying Cellular networks. - * - *

Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified - * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier. - */ - private NetworkRequest getCellNetworkRequestForSubId(int subId) { - return getBaseNetworkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) - .build(); - } - - /** - * Builds and returns a NetworkRequest builder common to all Underlying Network requests - */ - private NetworkRequest.Builder getBaseNetworkRequestBuilder() { - return 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); - } - - /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */ - private NetworkRequest getTestNetworkRequest(@NonNull Set subIds) { - return new NetworkRequest.Builder() - .clearCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_TEST) - .setSubscriptionIds(subIds) - .build(); - } - - /** - * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot. - * - *

Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to - * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered - * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change. - */ - public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot newSnapshot) { - Objects.requireNonNull(newSnapshot, "Missing newSnapshot"); - - final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot; - mLastSnapshot = newSnapshot; - - // Only trigger re-registration if subIds in this group have changed - if (oldSnapshot - .getAllSubIdsInGroup(mSubscriptionGroup) - .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) { - return; - } - registerOrUpdateNetworkRequests(); - } - - /** Tears down this Tracker, and releases all underlying network requests. */ - public void teardown() { - mVcnContext.ensureRunningOnLooperThread(); - mIsQuitting = true; - - // Will unregister all existing callbacks, but not register new ones due to quitting flag. - registerOrUpdateNetworkRequests(); - - mVcnContext - .getContext() - .getSystemService(TelephonyManager.class) - .unregisterTelephonyCallback(mActiveDataSubIdListener); - } - - private void reevaluateNetworks() { - if (mIsQuitting || mRouteSelectionCallback == null) { - return; // UnderlyingNetworkTracker has quit. - } - - TreeSet sorted = - mRouteSelectionCallback.getSortedUnderlyingNetworks(); - UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first(); - if (Objects.equals(mCurrentRecord, candidate)) { - return; - } - - mCurrentRecord = candidate; - mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord); - } - - private static boolean isOpportunistic( - @NonNull TelephonySubscriptionSnapshot snapshot, Set subIds) { - if (snapshot == null) { - logWtf("Got null snapshot"); - return false; - } - - for (int subId : subIds) { - if (snapshot.isOpportunistic(subId)) { - return true; - } - } - - return false; - } - - /** - * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped. - * - *

NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being - * reaped, and no action is taken on any events firing. - */ - @VisibleForTesting - class NetworkBringupCallback extends NetworkCallback {} - - /** - * RouteSelectionCallback is used to select the "best" underlying Network. - * - *

The "best" network is determined by ConnectivityService, which is treated as a source of - * truth. - */ - @VisibleForTesting - class UnderlyingNetworkListener extends NetworkCallback { - private final Map - mUnderlyingNetworkRecordBuilders = new ArrayMap<>(); - - UnderlyingNetworkListener() { - super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO); - } - - private TreeSet getSortedUnderlyingNetworks() { - TreeSet sorted = - new TreeSet<>( - UnderlyingNetworkRecord.getComparator( - mSubscriptionGroup, - mLastSnapshot, - mCurrentRecord, - mCarrierConfig)); - - for (UnderlyingNetworkRecord.Builder builder : - mUnderlyingNetworkRecordBuilders.values()) { - if (builder.isValid()) { - sorted.add(builder.build()); - } - } - - return sorted; - } - - @Override - public void onAvailable(@NonNull Network network) { - mUnderlyingNetworkRecordBuilders.put( - network, new UnderlyingNetworkRecord.Builder(network)); - } - - @Override - public void onLost(@NonNull Network network) { - mUnderlyingNetworkRecordBuilders.remove(network); - - reevaluateNetworks(); - } - - @Override - public void onCapabilitiesChanged( - @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { - final UnderlyingNetworkRecord.Builder builder = - mUnderlyingNetworkRecordBuilders.get(network); - if (builder == null) { - logWtf("Got capabilities change for unknown key: " + network); - return; - } - - builder.setNetworkCapabilities(networkCapabilities); - if (builder.isValid()) { - reevaluateNetworks(); - } - } - - @Override - public void onLinkPropertiesChanged( - @NonNull Network network, @NonNull LinkProperties linkProperties) { - final UnderlyingNetworkRecord.Builder builder = - mUnderlyingNetworkRecordBuilders.get(network); - if (builder == null) { - logWtf("Got link properties change for unknown key: " + network); - return; - } - - builder.setLinkProperties(linkProperties); - if (builder.isValid()) { - reevaluateNetworks(); - } - } - - @Override - public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) { - final UnderlyingNetworkRecord.Builder builder = - mUnderlyingNetworkRecordBuilders.get(network); - if (builder == null) { - logWtf("Got blocked status change for unknown key: " + network); - return; - } - - builder.setIsBlocked(isBlocked); - if (builder.isValid()) { - reevaluateNetworks(); - } - } - } - - private static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) { - if (carrierConfig != null) { - return carrierConfig.getInt( - VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, - WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT); - } - - return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; - } - - private static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) { - if (carrierConfig != null) { - return carrierConfig.getInt( - VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, - WIFI_EXIT_RSSI_THRESHOLD_DEFAULT); - } - - return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; - } - - /** A record of a single underlying network, caching relevant fields. */ - public static class UnderlyingNetworkRecord { - @NonNull public final Network network; - @NonNull public final NetworkCapabilities networkCapabilities; - @NonNull public final LinkProperties linkProperties; - public final boolean isBlocked; - - @VisibleForTesting(visibility = Visibility.PRIVATE) - UnderlyingNetworkRecord( - @NonNull Network network, - @NonNull NetworkCapabilities networkCapabilities, - @NonNull LinkProperties linkProperties, - boolean isBlocked) { - this.network = network; - this.networkCapabilities = networkCapabilities; - this.linkProperties = linkProperties; - this.isBlocked = isBlocked; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof UnderlyingNetworkRecord)) return false; - final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o; - - return network.equals(that.network) - && networkCapabilities.equals(that.networkCapabilities) - && linkProperties.equals(that.linkProperties) - && isBlocked == that.isBlocked; - } - - @Override - public int hashCode() { - return Objects.hash(network, networkCapabilities, linkProperties, isBlocked); - } - - /** - * Gives networks a priority class, based on the following priorities: - * - *

    - *
  1. Opportunistic cellular - *
  2. Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT - *
  3. Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT - *
  4. Macro cellular - *
  5. Any others - *
- */ - private int calculatePriorityClass( - ParcelUuid subscriptionGroup, - TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { - final NetworkCapabilities caps = networkCapabilities; - - // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED - - if (isBlocked) { - logWtf("Network blocked for System Server: " + network); - return PRIORITY_ANY; - } - - if (caps.hasTransport(TRANSPORT_CELLULAR) - && isOpportunistic(snapshot, caps.getSubscriptionIds())) { - // If this carrier is the active data provider, ensure that opportunistic is only - // ever prioritized if it is also the active data subscription. This ensures that - // if an opportunistic subscription is still in the process of being switched to, - // or switched away from, the VCN does not attempt to continue using it against the - // decision made at the telephony layer. Failure to do so may result in the modem - // switching back and forth. - // - // Allow the following two cases: - // 1. Active subId is NOT in the group that this VCN is supporting - // 2. This opportunistic subscription is for the active subId - if (!snapshot.getAllSubIdsInGroup(subscriptionGroup) - .contains(SubscriptionManager.getActiveDataSubscriptionId()) - || caps.getSubscriptionIds() - .contains(SubscriptionManager.getActiveDataSubscriptionId())) { - return PRIORITY_OPPORTUNISTIC_CELLULAR; - } - } - - if (caps.hasTransport(TRANSPORT_WIFI)) { - if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig) - && currentlySelected != null - && network.equals(currentlySelected.network)) { - return PRIORITY_WIFI_IN_USE; - } - - if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { - return PRIORITY_WIFI_PROSPECTIVE; - } - } - - // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might - // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be - // the case if the Default Data SubId does not support certain services (eg voice - // calling) - if (caps.hasTransport(TRANSPORT_CELLULAR) - && !isOpportunistic(snapshot, caps.getSubscriptionIds())) { - return PRIORITY_MACRO_CELLULAR; - } - - return PRIORITY_ANY; - } - - private static Comparator getComparator( - ParcelUuid subscriptionGroup, - TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { - return (left, right) -> { - return Integer.compare( - left.calculatePriorityClass( - subscriptionGroup, snapshot, currentlySelected, carrierConfig), - right.calculatePriorityClass( - subscriptionGroup, snapshot, currentlySelected, carrierConfig)); - }; - } - - /** Dumps the state of this record for logging and debugging purposes. */ - private void dump( - IndentingPrintWriter pw, - ParcelUuid subscriptionGroup, - TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { - pw.println("UnderlyingNetworkRecord:"); - pw.increaseIndent(); - - final int priorityClass = - calculatePriorityClass( - subscriptionGroup, snapshot, currentlySelected, carrierConfig); - pw.println( - "Priority class: " + PRIORITY_TO_STRING_MAP.get(priorityClass) + " (" - + priorityClass + ")"); - pw.println("mNetwork: " + network); - pw.println("mNetworkCapabilities: " + networkCapabilities); - pw.println("mLinkProperties: " + linkProperties); - - pw.decreaseIndent(); - } - - /** Builder to incrementally construct an UnderlyingNetworkRecord. */ - private static class Builder { - @NonNull private final Network mNetwork; - - @Nullable private NetworkCapabilities mNetworkCapabilities; - @Nullable private LinkProperties mLinkProperties; - boolean mIsBlocked; - boolean mWasIsBlockedSet; - - @Nullable private UnderlyingNetworkRecord mCached; - - private Builder(@NonNull Network network) { - mNetwork = network; - } - - @NonNull - private Network getNetwork() { - return mNetwork; - } - - private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { - mNetworkCapabilities = networkCapabilities; - mCached = null; - } - - @Nullable - private NetworkCapabilities getNetworkCapabilities() { - return mNetworkCapabilities; - } - - private void setLinkProperties(@NonNull LinkProperties linkProperties) { - mLinkProperties = linkProperties; - mCached = null; - } - - private void setIsBlocked(boolean isBlocked) { - mIsBlocked = isBlocked; - mWasIsBlockedSet = true; - mCached = null; - } - - private boolean isValid() { - return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet; - } - - private UnderlyingNetworkRecord build() { - if (!isValid()) { - throw new IllegalArgumentException( - "Called build before UnderlyingNetworkRecord was valid"); - } - - if (mCached == null) { - mCached = - new UnderlyingNetworkRecord( - mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked); - } - - return mCached; - } - } - } - - private static void logWtf(String msg) { - Slog.wtf(TAG, msg); - LOCAL_LOG.log(TAG + " WTF: " + msg); - } - - private static void logWtf(String msg, Throwable tr) { - Slog.wtf(TAG, msg, tr); - LOCAL_LOG.log(TAG + " WTF: " + msg + tr); - } - - /** Dumps the state of this record for logging and debugging purposes. */ - public void dump(IndentingPrintWriter pw) { - pw.println("UnderlyingNetworkTracker:"); - pw.increaseIndent(); - - pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig)); - pw.println("Carrier WiFi Exit Threshold: " + getWifiExitRssiThreshold(mCarrierConfig)); - pw.println( - "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network)); - - pw.println("Underlying networks:"); - pw.increaseIndent(); - if (mRouteSelectionCallback != null) { - for (UnderlyingNetworkRecord record : - mRouteSelectionCallback.getSortedUnderlyingNetworks()) { - record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig); - } - } - pw.decreaseIndent(); - pw.println(); - - pw.decreaseIndent(); - } - - private class VcnActiveDataSubscriptionIdListener extends TelephonyCallback - implements ActiveDataSubscriptionIdListener { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - reevaluateNetworks(); - } - } - - /** Callbacks for being notified of the changes in, or to the selected underlying network. */ - public interface UnderlyingNetworkTrackerCallback { - /** - * Fired when a new underlying network is selected, or properties have changed. - * - *

This callback does NOT signal a mobility event. - * - * @param underlyingNetworkRecord The details of the new underlying network - */ - void onSelectedUnderlyingNetworkChanged( - @Nullable UnderlyingNetworkRecord underlyingNetworkRecord); - } - - private static class Dependencies {} -} diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 1c46ac8c4aa1..886127c2b6dc 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -87,9 +87,10 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkRecord; import com.android.server.vcn.util.LogUtils; import com.android.server.vcn.util.MtuUtils; import com.android.server.vcn.util.OneWayBoolean; @@ -201,7 +202,7 @@ public class VcnGatewayConnection extends StateMachine { private interface EventInfo {} /** - * Sent when there are changes to the underlying network (per the UnderlyingNetworkTracker). + * Sent when there are changes to the underlying network (per the UnderlyingNetworkController). * *

May indicate an entirely new underlying network, OR a change in network properties. * @@ -522,11 +523,14 @@ public class VcnGatewayConnection extends StateMachine { @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; - @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + @NonNull private final UnderlyingNetworkController mUnderlyingNetworkController; @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull private final Dependencies mDeps; - @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback; + + @NonNull + private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback; + private final boolean mIsMobileDataEnabled; @NonNull private final IpSecManager mIpSecManager; @@ -674,17 +678,17 @@ public class VcnGatewayConnection extends StateMachine { mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); - mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); + mUnderlyingNetworkControllerCallback = new VcnUnderlyingNetworkControllerCallback(); mWakeLock = mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG); - mUnderlyingNetworkTracker = - mDeps.newUnderlyingNetworkTracker( + mUnderlyingNetworkController = + mDeps.newUnderlyingNetworkController( mVcnContext, subscriptionGroup, mLastSnapshot, - mUnderlyingNetworkTrackerCallback); + mUnderlyingNetworkControllerCallback); mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); addState(mDisconnectedState); @@ -748,7 +752,7 @@ public class VcnGatewayConnection extends StateMachine { cancelRetryTimeoutAlarm(); cancelSafeModeAlarm(); - mUnderlyingNetworkTracker.teardown(); + mUnderlyingNetworkController.teardown(); mGatewayStatusCallback.onQuit(); } @@ -764,12 +768,13 @@ public class VcnGatewayConnection extends StateMachine { mVcnContext.ensureRunningOnLooperThread(); mLastSnapshot = snapshot; - mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); + mUnderlyingNetworkController.updateSubscriptionSnapshot(mLastSnapshot); sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); } - private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { + private class VcnUnderlyingNetworkControllerCallback + implements UnderlyingNetworkControllerCallback { @Override public void onSelectedUnderlyingNetworkChanged( @Nullable UnderlyingNetworkRecord underlying) { @@ -2264,7 +2269,7 @@ public class VcnGatewayConnection extends StateMachine { + (mNetworkAgent == null ? null : mNetworkAgent.getNetwork())); pw.println(); - mUnderlyingNetworkTracker.dump(pw); + mUnderlyingNetworkController.dump(pw); pw.println(); pw.decreaseIndent(); @@ -2276,8 +2281,8 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) - UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() { - return mUnderlyingNetworkTrackerCallback; + UnderlyingNetworkControllerCallback getUnderlyingNetworkControllerCallback() { + return mUnderlyingNetworkControllerCallback; } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -2356,17 +2361,14 @@ public class VcnGatewayConnection extends StateMachine { /** External dependencies used by VcnGatewayConnection, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { - /** Builds a new UnderlyingNetworkTracker. */ - public UnderlyingNetworkTracker newUnderlyingNetworkTracker( + /** Builds a new UnderlyingNetworkController. */ + public UnderlyingNetworkController newUnderlyingNetworkController( VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkTrackerCallback callback) { - return new UnderlyingNetworkTracker( - vcnContext, - subscriptionGroup, - snapshot, - callback); + UnderlyingNetworkControllerCallback callback) { + return new UnderlyingNetworkController( + vcnContext, subscriptionGroup, snapshot, callback); } /** Builds a new IkeSession. */ diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java new file mode 100644 index 000000000000..b36d4fea3a2f --- /dev/null +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -0,0 +1,828 @@ +/* + * 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.vcn.routeselection; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; + +import static com.android.server.VcnManagementService.LOCAL_LOG; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnManager; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.ParcelUuid; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnContext; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +/** + * Tracks a set of Networks underpinning a VcnGatewayConnection. + * + *

A single UnderlyingNetworkController is built to serve a SINGLE VCN Gateway Connection, and + * MUST be torn down with the VcnGatewayConnection in order to ensure underlying networks are + * allowed to be reaped. + * + * @hide + */ +public class UnderlyingNetworkController { + @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName(); + + /** + * Minimum signal strength for a WiFi network to be eligible for switching to + * + *

A network that satisfies this is eligible to become the selected underlying network with + * no additional conditions + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70; + + /** + * Minimum signal strength to continue using a WiFi network + * + *

A network that satisfies the conditions may ONLY continue to be used if it is already + * selected as the underlying network. A WiFi network satisfying this condition, but NOT the + * prospective-network RSSI threshold CANNOT be switched to. + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74; + + /** Priority for any cellular network for which the subscription is listed as opportunistic */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0; + + /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_WIFI_IN_USE = 1; + + /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_WIFI_PROSPECTIVE = 2; + + /** Priority for any standard macro cellular network */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_MACRO_CELLULAR = 3; + + /** Priority for any other networks (including unvalidated, etc) */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_ANY = Integer.MAX_VALUE; + + private static final SparseArray PRIORITY_TO_STRING_MAP = new SparseArray<>(); + + static { + PRIORITY_TO_STRING_MAP.put( + PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY"); + } + + @NonNull private final VcnContext mVcnContext; + @NonNull private final ParcelUuid mSubscriptionGroup; + @NonNull private final UnderlyingNetworkControllerCallback mCb; + @NonNull private final Dependencies mDeps; + @NonNull private final Handler mHandler; + @NonNull private final ConnectivityManager mConnectivityManager; + @NonNull private final TelephonyCallback mActiveDataSubIdListener = + new VcnActiveDataSubscriptionIdListener(); + + @NonNull private final List mCellBringupCallbacks = new ArrayList<>(); + @Nullable private NetworkCallback mWifiBringupCallback; + @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback; + @Nullable private NetworkCallback mWifiExitRssiThresholdCallback; + @Nullable private UnderlyingNetworkListener mRouteSelectionCallback; + + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; + @Nullable private PersistableBundle mCarrierConfig; + private boolean mIsQuitting = false; + + @Nullable private UnderlyingNetworkRecord mCurrentRecord; + @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; + + public UnderlyingNetworkController( + @NonNull VcnContext vcnContext, + @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull UnderlyingNetworkControllerCallback cb) { + this( + vcnContext, + subscriptionGroup, + snapshot, + cb, + new Dependencies()); + } + + private UnderlyingNetworkController( + @NonNull VcnContext vcnContext, + @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull UnderlyingNetworkControllerCallback cb, + @NonNull Dependencies deps) { + mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); + mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); + mCb = Objects.requireNonNull(cb, "Missing cb"); + mDeps = Objects.requireNonNull(deps, "Missing deps"); + + mHandler = new Handler(mVcnContext.getLooper()); + + mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class); + mVcnContext + .getContext() + .getSystemService(TelephonyManager.class) + .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener); + + // TODO: Listen for changes in carrier config that affect this. + for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { + PersistableBundle config = + mVcnContext + .getContext() + .getSystemService(CarrierConfigManager.class) + .getConfigForSubId(subId); + + if (config != null) { + mCarrierConfig = config; + + // Attempt to use (any) non-opportunistic subscription. If this subscription is + // opportunistic, continue and try to find a non-opportunistic subscription, using + // the opportunistic ones as a last resort. + if (!isOpportunistic(mLastSnapshot, Collections.singleton(subId))) { + break; + } + } + } + + registerOrUpdateNetworkRequests(); + } + + private void registerOrUpdateNetworkRequests() { + NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback; + NetworkCallback oldWifiCallback = mWifiBringupCallback; + NetworkCallback oldWifiEntryRssiThresholdCallback = mWifiEntryRssiThresholdCallback; + NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback; + List oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks); + mCellBringupCallbacks.clear(); + + // Register new callbacks. Make-before-break; always register new callbacks before removal + // of old callbacks + if (!mIsQuitting) { + mRouteSelectionCallback = new UnderlyingNetworkListener(); + mConnectivityManager.registerNetworkCallback( + getRouteSelectionRequest(), mRouteSelectionCallback, mHandler); + + mWifiEntryRssiThresholdCallback = new NetworkBringupCallback(); + mConnectivityManager.registerNetworkCallback( + getWifiEntryRssiThresholdNetworkRequest(), + mWifiEntryRssiThresholdCallback, + mHandler); + + mWifiExitRssiThresholdCallback = new NetworkBringupCallback(); + mConnectivityManager.registerNetworkCallback( + getWifiExitRssiThresholdNetworkRequest(), + mWifiExitRssiThresholdCallback, + mHandler); + + mWifiBringupCallback = new NetworkBringupCallback(); + mConnectivityManager.requestBackgroundNetwork( + getWifiNetworkRequest(), mWifiBringupCallback, mHandler); + + for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { + final NetworkBringupCallback cb = new NetworkBringupCallback(); + mCellBringupCallbacks.add(cb); + + mConnectivityManager.requestBackgroundNetwork( + getCellNetworkRequestForSubId(subId), cb, mHandler); + } + } else { + mRouteSelectionCallback = null; + mWifiBringupCallback = null; + mWifiEntryRssiThresholdCallback = null; + mWifiExitRssiThresholdCallback = null; + // mCellBringupCallbacks already cleared above. + } + + // Unregister old callbacks (as necessary) + if (oldRouteSelectionCallback != null) { + mConnectivityManager.unregisterNetworkCallback(oldRouteSelectionCallback); + } + if (oldWifiCallback != null) { + mConnectivityManager.unregisterNetworkCallback(oldWifiCallback); + } + if (oldWifiEntryRssiThresholdCallback != null) { + mConnectivityManager.unregisterNetworkCallback(oldWifiEntryRssiThresholdCallback); + } + if (oldWifiExitRssiThresholdCallback != null) { + mConnectivityManager.unregisterNetworkCallback(oldWifiExitRssiThresholdCallback); + } + for (NetworkCallback cellBringupCallback : oldCellCallbacks) { + mConnectivityManager.unregisterNetworkCallback(cellBringupCallback); + } + } + + /** + * Builds the Route selection request + * + *

This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue + * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only + * carrier owned networks may be selected, as the request specifies only subIds in the VCN's + * subscription group, while the VCN networks are excluded by virtue of not having subIds set on + * the VCN-exposed networks. + * + *

If the VCN that this UnderlyingNetworkController belongs to is in test-mode, this will + * return a NetworkRequest that only matches Test Networks. + */ + private NetworkRequest getRouteSelectionRequest() { + if (mVcnContext.isInTestMode()) { + return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)); + } + + return getBaseNetworkRequestBuilder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + .build(); + } + + /** + * Builds the WiFi bringup request + * + *

This request is built specifically to match only carrier-owned WiFi networks, but is also + * built to ONLY keep Carrier WiFi Networks alive (but never bring them up). This is a result of + * the WifiNetworkFactory not advertising a list of subIds, and therefore not accepting this + * request. As such, it will bind to a Carrier WiFi Network that has already been brought up, + * but will NEVER bring up a Carrier WiFi network itself. + */ + private NetworkRequest getWifiNetworkRequest() { + return getBaseNetworkRequestBuilder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + .build(); + } + + /** + * Builds the WiFi entry threshold signal strength request + * + *

This request ensures that WiFi reports the crossing of the wifi entry RSSI threshold. + * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a + * pace to effectively select a short-lived WiFi offload network. + */ + private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() { + return getBaseNetworkRequestBuilder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + // Ensure wifi updates signal strengths when crossing this threshold. + .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig)) + .build(); + } + + /** + * Builds the WiFi exit threshold signal strength request + * + *

This request ensures that WiFi reports the crossing of the wifi exit RSSI threshold. + * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a + * pace to effectively select away from a failing WiFi network. + */ + private NetworkRequest getWifiExitRssiThresholdNetworkRequest() { + return getBaseNetworkRequestBuilder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) + // Ensure wifi updates signal strengths when crossing this threshold. + .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig)) + .build(); + } + + /** + * Builds a Cellular bringup request for a given subId + * + *

This request is filed in order to ensure that the Telephony stack always has a + * NetworkRequest to bring up a VCN underlying cellular network. It is required in order to + * ensure that even when a VCN (appears as Cellular) satisfies the default request, Telephony + * will bring up additional underlying Cellular networks. + * + *

Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified + * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier. + */ + private NetworkRequest getCellNetworkRequestForSubId(int subId) { + return getBaseNetworkRequestBuilder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) + .build(); + } + + /** + * Builds and returns a NetworkRequest builder common to all Underlying Network requests + */ + private NetworkRequest.Builder getBaseNetworkRequestBuilder() { + return 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); + } + + /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */ + private NetworkRequest getTestNetworkRequest(@NonNull Set subIds) { + return new NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST) + .setSubscriptionIds(subIds) + .build(); + } + + /** + * Update this UnderlyingNetworkController's TelephonySubscriptionSnapshot. + * + *

Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkController to + * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered + * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change. + */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot newSnapshot) { + Objects.requireNonNull(newSnapshot, "Missing newSnapshot"); + + final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot; + mLastSnapshot = newSnapshot; + + // Only trigger re-registration if subIds in this group have changed + if (oldSnapshot + .getAllSubIdsInGroup(mSubscriptionGroup) + .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) { + return; + } + registerOrUpdateNetworkRequests(); + } + + /** Tears down this Tracker, and releases all underlying network requests. */ + public void teardown() { + mVcnContext.ensureRunningOnLooperThread(); + mIsQuitting = true; + + // Will unregister all existing callbacks, but not register new ones due to quitting flag. + registerOrUpdateNetworkRequests(); + + mVcnContext + .getContext() + .getSystemService(TelephonyManager.class) + .unregisterTelephonyCallback(mActiveDataSubIdListener); + } + + private void reevaluateNetworks() { + if (mIsQuitting || mRouteSelectionCallback == null) { + return; // UnderlyingNetworkController has quit. + } + + TreeSet sorted = + mRouteSelectionCallback.getSortedUnderlyingNetworks(); + UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first(); + if (Objects.equals(mCurrentRecord, candidate)) { + return; + } + + mCurrentRecord = candidate; + mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord); + } + + private static boolean isOpportunistic( + @NonNull TelephonySubscriptionSnapshot snapshot, Set subIds) { + if (snapshot == null) { + logWtf("Got null snapshot"); + return false; + } + + for (int subId : subIds) { + if (snapshot.isOpportunistic(subId)) { + return true; + } + } + + return false; + } + + /** + * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped. + * + *

NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being + * reaped, and no action is taken on any events firing. + */ + @VisibleForTesting + class NetworkBringupCallback extends NetworkCallback {} + + /** + * RouteSelectionCallback is used to select the "best" underlying Network. + * + *

The "best" network is determined by ConnectivityService, which is treated as a source of + * truth. + */ + @VisibleForTesting + class UnderlyingNetworkListener extends NetworkCallback { + private final Map + mUnderlyingNetworkRecordBuilders = new ArrayMap<>(); + + UnderlyingNetworkListener() { + super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO); + } + + private TreeSet getSortedUnderlyingNetworks() { + TreeSet sorted = + new TreeSet<>( + UnderlyingNetworkRecord.getComparator( + mSubscriptionGroup, + mLastSnapshot, + mCurrentRecord, + mCarrierConfig)); + + for (UnderlyingNetworkRecord.Builder builder : + mUnderlyingNetworkRecordBuilders.values()) { + if (builder.isValid()) { + sorted.add(builder.build()); + } + } + + return sorted; + } + + @Override + public void onAvailable(@NonNull Network network) { + mUnderlyingNetworkRecordBuilders.put( + network, new UnderlyingNetworkRecord.Builder(network)); + } + + @Override + public void onLost(@NonNull Network network) { + mUnderlyingNetworkRecordBuilders.remove(network); + + reevaluateNetworks(); + } + + @Override + public void onCapabilitiesChanged( + @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { + final UnderlyingNetworkRecord.Builder builder = + mUnderlyingNetworkRecordBuilders.get(network); + if (builder == null) { + logWtf("Got capabilities change for unknown key: " + network); + return; + } + + builder.setNetworkCapabilities(networkCapabilities); + if (builder.isValid()) { + reevaluateNetworks(); + } + } + + @Override + public void onLinkPropertiesChanged( + @NonNull Network network, @NonNull LinkProperties linkProperties) { + final UnderlyingNetworkRecord.Builder builder = + mUnderlyingNetworkRecordBuilders.get(network); + if (builder == null) { + logWtf("Got link properties change for unknown key: " + network); + return; + } + + builder.setLinkProperties(linkProperties); + if (builder.isValid()) { + reevaluateNetworks(); + } + } + + @Override + public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) { + final UnderlyingNetworkRecord.Builder builder = + mUnderlyingNetworkRecordBuilders.get(network); + if (builder == null) { + logWtf("Got blocked status change for unknown key: " + network); + return; + } + + builder.setIsBlocked(isBlocked); + if (builder.isValid()) { + reevaluateNetworks(); + } + } + } + + private static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) { + if (carrierConfig != null) { + return carrierConfig.getInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, + WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT); + } + + return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; + } + + private static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) { + if (carrierConfig != null) { + return carrierConfig.getInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, + WIFI_EXIT_RSSI_THRESHOLD_DEFAULT); + } + + return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; + } + + /** A record of a single underlying network, caching relevant fields. */ + public static class UnderlyingNetworkRecord { + @NonNull public final Network network; + @NonNull public final NetworkCapabilities networkCapabilities; + @NonNull public final LinkProperties linkProperties; + public final boolean isBlocked; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public UnderlyingNetworkRecord( + @NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, + boolean isBlocked) { + this.network = network; + this.networkCapabilities = networkCapabilities; + this.linkProperties = linkProperties; + this.isBlocked = isBlocked; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UnderlyingNetworkRecord)) return false; + final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o; + + return network.equals(that.network) + && networkCapabilities.equals(that.networkCapabilities) + && linkProperties.equals(that.linkProperties) + && isBlocked == that.isBlocked; + } + + @Override + public int hashCode() { + return Objects.hash(network, networkCapabilities, linkProperties, isBlocked); + } + + /** + * Gives networks a priority class, based on the following priorities: + * + *

    + *
  1. Opportunistic cellular + *
  2. Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT + *
  3. Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT + *
  4. Macro cellular + *
  5. Any others + *
+ */ + private int calculatePriorityClass( + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + final NetworkCapabilities caps = networkCapabilities; + + // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED + + if (isBlocked) { + logWtf("Network blocked for System Server: " + network); + return PRIORITY_ANY; + } + + if (caps.hasTransport(TRANSPORT_CELLULAR) + && isOpportunistic(snapshot, caps.getSubscriptionIds())) { + // If this carrier is the active data provider, ensure that opportunistic is only + // ever prioritized if it is also the active data subscription. This ensures that + // if an opportunistic subscription is still in the process of being switched to, + // or switched away from, the VCN does not attempt to continue using it against the + // decision made at the telephony layer. Failure to do so may result in the modem + // switching back and forth. + // + // Allow the following two cases: + // 1. Active subId is NOT in the group that this VCN is supporting + // 2. This opportunistic subscription is for the active subId + if (!snapshot.getAllSubIdsInGroup(subscriptionGroup) + .contains(SubscriptionManager.getActiveDataSubscriptionId()) + || caps.getSubscriptionIds() + .contains(SubscriptionManager.getActiveDataSubscriptionId())) { + return PRIORITY_OPPORTUNISTIC_CELLULAR; + } + } + + if (caps.hasTransport(TRANSPORT_WIFI)) { + if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig) + && currentlySelected != null + && network.equals(currentlySelected.network)) { + return PRIORITY_WIFI_IN_USE; + } + + if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { + return PRIORITY_WIFI_PROSPECTIVE; + } + } + + // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might + // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be + // the case if the Default Data SubId does not support certain services (eg voice + // calling) + if (caps.hasTransport(TRANSPORT_CELLULAR) + && !isOpportunistic(snapshot, caps.getSubscriptionIds())) { + return PRIORITY_MACRO_CELLULAR; + } + + return PRIORITY_ANY; + } + + private static Comparator getComparator( + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + return (left, right) -> { + return Integer.compare( + left.calculatePriorityClass( + subscriptionGroup, snapshot, currentlySelected, carrierConfig), + right.calculatePriorityClass( + subscriptionGroup, snapshot, currentlySelected, carrierConfig)); + }; + } + + /** Dumps the state of this record for logging and debugging purposes. */ + private void dump( + IndentingPrintWriter pw, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + pw.println("UnderlyingNetworkRecord:"); + pw.increaseIndent(); + + final int priorityClass = + calculatePriorityClass( + subscriptionGroup, snapshot, currentlySelected, carrierConfig); + pw.println( + "Priority class: " + PRIORITY_TO_STRING_MAP.get(priorityClass) + " (" + + priorityClass + ")"); + pw.println("mNetwork: " + network); + pw.println("mNetworkCapabilities: " + networkCapabilities); + pw.println("mLinkProperties: " + linkProperties); + + pw.decreaseIndent(); + } + + /** Builder to incrementally construct an UnderlyingNetworkRecord. */ + private static class Builder { + @NonNull private final Network mNetwork; + + @Nullable private NetworkCapabilities mNetworkCapabilities; + @Nullable private LinkProperties mLinkProperties; + boolean mIsBlocked; + boolean mWasIsBlockedSet; + + @Nullable private UnderlyingNetworkRecord mCached; + + private Builder(@NonNull Network network) { + mNetwork = network; + } + + @NonNull + private Network getNetwork() { + return mNetwork; + } + + private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { + mNetworkCapabilities = networkCapabilities; + mCached = null; + } + + @Nullable + private NetworkCapabilities getNetworkCapabilities() { + return mNetworkCapabilities; + } + + private void setLinkProperties(@NonNull LinkProperties linkProperties) { + mLinkProperties = linkProperties; + mCached = null; + } + + private void setIsBlocked(boolean isBlocked) { + mIsBlocked = isBlocked; + mWasIsBlockedSet = true; + mCached = null; + } + + private boolean isValid() { + return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet; + } + + private UnderlyingNetworkRecord build() { + if (!isValid()) { + throw new IllegalArgumentException( + "Called build before UnderlyingNetworkRecord was valid"); + } + + if (mCached == null) { + mCached = + new UnderlyingNetworkRecord( + mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked); + } + + return mCached; + } + } + } + + private static void logWtf(String msg) { + Slog.wtf(TAG, msg); + LOCAL_LOG.log(TAG + " WTF: " + msg); + } + + private static void logWtf(String msg, Throwable tr) { + Slog.wtf(TAG, msg, tr); + LOCAL_LOG.log(TAG + " WTF: " + msg + tr); + } + + /** Dumps the state of this record for logging and debugging purposes. */ + public void dump(IndentingPrintWriter pw) { + pw.println("UnderlyingNetworkController:"); + pw.increaseIndent(); + + pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig)); + pw.println("Carrier WiFi Exit Threshold: " + getWifiExitRssiThreshold(mCarrierConfig)); + pw.println( + "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network)); + + pw.println("Underlying networks:"); + pw.increaseIndent(); + if (mRouteSelectionCallback != null) { + for (UnderlyingNetworkRecord record : + mRouteSelectionCallback.getSortedUnderlyingNetworks()) { + record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig); + } + } + pw.decreaseIndent(); + pw.println(); + + pw.decreaseIndent(); + } + + private class VcnActiveDataSubscriptionIdListener extends TelephonyCallback + implements ActiveDataSubscriptionIdListener { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + reevaluateNetworks(); + } + } + + /** Callbacks for being notified of the changes in, or to the selected underlying network. */ + public interface UnderlyingNetworkControllerCallback { + /** + * Fired when a new underlying network is selected, or properties have changed. + * + *

This callback does NOT signal a mobility event. + * + * @param underlyingNetworkRecord The details of the new underlying network + */ + void onSelectedUnderlyingNetworkChanged( + @Nullable UnderlyingNetworkRecord underlyingNetworkRecord); + } + + private static class Dependencies {} +} diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java deleted file mode 100644 index 5af69b5d1bf2..000000000000 --- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * 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.CarrierConfigManager; -import android.telephony.SubscriptionInfo; -import android.telephony.TelephonyManager; -import android.util.ArraySet; - -import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkListener; -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.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 INITIAL_SUB_IDS = - new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2)); - private static final Set 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 TelephonyManager mTelephonyManager; - @Mock private CarrierConfigManager mCarrierConfigManager; - @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; - @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb; - @Mock private Network mNetwork; - - @Captor private ArgumentCaptor mUnderlyingNetworkListenerCaptor; - - 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); - setupSystemService( - mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class); - setupSystemService( - mContext, - mCarrierConfigManager, - Context.CARRIER_CONFIG_SERVICE, - CarrierConfigManager.class); - - when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS); - - mUnderlyingNetworkTracker = - new UnderlyingNetworkTracker( - mVcnContext, - SUB_GROUP, - mSubscriptionSnapshot, - 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() { - final ConnectivityManager cm = mock(ConnectivityManager.class); - setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); - final VcnContext vcnContext = - new VcnContext( - mContext, - mTestLooper.getLooper(), - mVcnNetworkProvider, - true /* isInTestMode */); - - new UnderlyingNetworkTracker( - vcnContext, - SUB_GROUP, - mSubscriptionSnapshot, - mNetworkTrackerCb); - - verify(cm) - .registerNetworkCallback( - eq(getTestNetworkRequest(INITIAL_SUB_IDS)), - any(UnderlyingNetworkListener.class), - any()); - } - - private void verifyNetworkRequestsRegistered(Set expectedSubIds) { - 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()); - } - - verify(mConnectivityManager) - .registerNetworkCallback( - eq(getRouteSelectionRequest(expectedSubIds)), - any(UnderlyingNetworkListener.class), - any()); - verify(mConnectivityManager) - .registerNetworkCallback( - eq(getWifiEntryRssiThresholdRequest(expectedSubIds)), - any(NetworkBringupCallback.class), - any()); - verify(mConnectivityManager) - .registerNetworkCallback( - eq(getWifiExitRssiThresholdRequest(expectedSubIds)), - any(NetworkBringupCallback.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() + 3)) - .unregisterNetworkCallback(any(NetworkBringupCallback.class)); - verify(mConnectivityManager) - .unregisterNetworkCallback(any(UnderlyingNetworkListener.class)); - verifyNetworkRequestsRegistered(UPDATED_SUB_IDS); - } - - private NetworkRequest getWifiRequest(Set netCapsSubIds) { - return getExpectedRequestBase() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(netCapsSubIds) - .build(); - } - - private NetworkRequest getWifiEntryRssiThresholdRequest(Set netCapsSubIds) { - // TODO (b/187991063): Add tests for carrier-config based thresholds - return getExpectedRequestBase() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(netCapsSubIds) - .setSignalStrength(UnderlyingNetworkTracker.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) - .build(); - } - - private NetworkRequest getWifiExitRssiThresholdRequest(Set netCapsSubIds) { - // TODO (b/187991063): Add tests for carrier-config based thresholds - return getExpectedRequestBase() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSubscriptionIds(netCapsSubIds) - .setSignalStrength(UnderlyingNetworkTracker.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) - .build(); - } - - private NetworkRequest getCellRequestForSubId(int subId) { - return getExpectedRequestBase() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) - .build(); - } - - private NetworkRequest getRouteSelectionRequest(Set netCapsSubIds) { - return getExpectedRequestBase() - .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) - .setSubscriptionIds(netCapsSubIds) - .build(); - } - - private NetworkRequest getTestNetworkRequest(Set netCapsSubIds) { - return new NetworkRequest.Builder() - .clearCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_TEST) - .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 5 NetworkBringupCallbacks to be unregistered: 1 for WiFi, 2 for Cellular (1x for - // each subId), and 1 for each of the Wifi signal strength thresholds - verify(mConnectivityManager, times(5)) - .unregisterNetworkCallback(any(NetworkBringupCallback.class)); - verify(mConnectivityManager) - .unregisterNetworkCallback(any(UnderlyingNetworkListener.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 UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() { - return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES); - } - - private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback( - NetworkCapabilities networkCapabilities) { - verify(mConnectivityManager) - .registerNetworkCallback( - eq(getRouteSelectionRequest(INITIAL_SUB_IDS)), - mUnderlyingNetworkListenerCaptor.capture(), - any()); - - UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.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() { - UnderlyingNetworkListener 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() { - UnderlyingNetworkListener 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() { - UnderlyingNetworkListener 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() { - UnderlyingNetworkListener 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() { - UnderlyingNetworkListener 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() { - UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - - cb.onLost(mNetwork); - - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null); - } - - @Test - public void testRecordTrackerCallbackIgnoresDuplicateRecord() { - UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - - cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); - - // Verify no more calls to the UnderlyingNetworkTrackerCallback when the - // UnderlyingNetworkRecord does not actually change - verifyNoMoreInteractions(mNetworkTrackerCb); - } - - @Test - public void testRecordTrackerCallbackNotifiedAfterTeardown() { - UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - mUnderlyingNetworkTracker.teardown(); - - cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); - - // Verify that the only call was during onAvailable() - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); - } - - // TODO (b/187991063): Add tests for network prioritization -} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 937f9dc60a72..15de226cdc40 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -119,7 +119,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); @@ -131,7 +131,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -143,7 +143,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testSameNetworkDoesNotTriggerMigration() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -203,7 +203,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection triggerChildOpened(); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); getChildSessionCallback() .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index d1f3a210d870..3c70759a2fa6 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -64,7 +64,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testNullNetworkTriggersDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); @@ -76,7 +76,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testNewNetworkTriggersReconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -89,7 +89,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testSameNetworkDoesNotTriggerReconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 2056eea42ce6..f3eb82f46de7 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -78,7 +78,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testNetworkChangesTriggerStateTransitions() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -89,7 +89,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testNullNetworkDoesNotTriggerStateTransition() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index 1c859790a2fe..6568cdd44377 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -58,7 +58,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testNewNetworkTriggerRetry() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -72,7 +72,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testSameNetworkDoesNotTriggerRetry() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -86,7 +86,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testNullNetworkTriggersDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index 2b0037eaf8eb..2d4eca8b0959 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -59,7 +59,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkRecord; import org.junit.Before; import org.junit.Test; @@ -238,14 +238,14 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { } @Test - public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() { + public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkController() { verifyWakeLockSetUp(); final TelephonySubscriptionSnapshot updatedSnapshot = mock(TelephonySubscriptionSnapshot.class); mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot); - verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot)); + verify(mUnderlyingNetworkController).updateSubscriptionSnapshot(eq(updatedSnapshot)); verifyWakeLockAcquired(); mTestLooper.dispatchAll(); @@ -256,13 +256,13 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { @Test public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); verify(mDisconnectRequestAlarm).cancel(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 64d0bca15ce9..0db9830ce4e8 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -16,10 +16,10 @@ package com.android.server.vcn; -import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; +import static com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkRecord; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -62,6 +62,7 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback; import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock; +import com.android.server.vcn.routeselection.UnderlyingNetworkController; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -137,7 +138,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final VcnGatewayConnectionConfig mConfig; @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull protected final VcnGatewayConnection.Dependencies mDeps; - @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + @NonNull protected final UnderlyingNetworkController mUnderlyingNetworkController; @NonNull protected final VcnWakeLock mWakeLock; @NonNull protected final WakeupMessage mTeardownTimeoutAlarm; @NonNull protected final WakeupMessage mDisconnectRequestAlarm; @@ -158,7 +159,7 @@ public class VcnGatewayConnectionTestBase { mConfig = VcnGatewayConnectionConfigTest.buildTestConfig(); mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class); mDeps = mock(VcnGatewayConnection.Dependencies.class); - mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); + mUnderlyingNetworkController = mock(UnderlyingNetworkController.class); mWakeLock = mock(VcnWakeLock.class); mTeardownTimeoutAlarm = mock(WakeupMessage.class); mDisconnectRequestAlarm = mock(WakeupMessage.class); @@ -176,9 +177,9 @@ public class VcnGatewayConnectionTestBase { doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); - doReturn(mUnderlyingNetworkTracker) + doReturn(mUnderlyingNetworkController) .when(mDeps) - .newUnderlyingNetworkTracker(any(), any(), any(), any()); + .newUnderlyingNetworkController(any(), any(), any(), any()); doReturn(mWakeLock) .when(mDeps) .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java new file mode 100644 index 000000000000..5b9544e3bead --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java @@ -0,0 +1,491 @@ +/* + * 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.routeselection; + +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.CarrierConfigManager; +import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyManager; +import android.util.ArraySet; + +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkRecord; + +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.Set; +import java.util.UUID; + +public class UnderlyingNetworkControllerTest { + 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 INITIAL_SUB_IDS = + new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2)); + private static final Set 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 TelephonyManager mTelephonyManager; + @Mock private CarrierConfigManager mCarrierConfigManager; + @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; + @Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb; + @Mock private Network mNetwork; + + @Captor private ArgumentCaptor mUnderlyingNetworkListenerCaptor; + + private TestLooper mTestLooper; + private VcnContext mVcnContext; + private UnderlyingNetworkController mUnderlyingNetworkController; + + @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); + setupSystemService( + mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class); + setupSystemService( + mContext, + mCarrierConfigManager, + Context.CARRIER_CONFIG_SERVICE, + CarrierConfigManager.class); + + when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS); + + mUnderlyingNetworkController = + new UnderlyingNetworkController( + mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); + } + + 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() { + final ConnectivityManager cm = mock(ConnectivityManager.class); + setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + final VcnContext vcnContext = + new VcnContext( + mContext, + mTestLooper.getLooper(), + mVcnNetworkProvider, + true /* isInTestMode */); + + new UnderlyingNetworkController( + vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); + + verify(cm) + .registerNetworkCallback( + eq(getTestNetworkRequest(INITIAL_SUB_IDS)), + any(UnderlyingNetworkListener.class), + any()); + } + + private void verifyNetworkRequestsRegistered(Set expectedSubIds) { + 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()); + } + + verify(mConnectivityManager) + .registerNetworkCallback( + eq(getRouteSelectionRequest(expectedSubIds)), + any(UnderlyingNetworkListener.class), + any()); + verify(mConnectivityManager) + .registerNetworkCallback( + eq(getWifiEntryRssiThresholdRequest(expectedSubIds)), + any(NetworkBringupCallback.class), + any()); + verify(mConnectivityManager) + .registerNetworkCallback( + eq(getWifiExitRssiThresholdRequest(expectedSubIds)), + any(NetworkBringupCallback.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); + + mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate); + + // verify that initially-filed bringup requests are unregistered (cell + wifi) + verify(mConnectivityManager, times(INITIAL_SUB_IDS.size() + 3)) + .unregisterNetworkCallback(any(NetworkBringupCallback.class)); + verify(mConnectivityManager) + .unregisterNetworkCallback(any(UnderlyingNetworkListener.class)); + verifyNetworkRequestsRegistered(UPDATED_SUB_IDS); + } + + private NetworkRequest getWifiRequest(Set netCapsSubIds) { + return getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSubscriptionIds(netCapsSubIds) + .build(); + } + + private NetworkRequest getWifiEntryRssiThresholdRequest(Set netCapsSubIds) { + // TODO (b/187991063): Add tests for carrier-config based thresholds + return getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSubscriptionIds(netCapsSubIds) + .setSignalStrength(UnderlyingNetworkController.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) + .build(); + } + + private NetworkRequest getWifiExitRssiThresholdRequest(Set netCapsSubIds) { + // TODO (b/187991063): Add tests for carrier-config based thresholds + return getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setSubscriptionIds(netCapsSubIds) + .setSignalStrength(UnderlyingNetworkController.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) + .build(); + } + + private NetworkRequest getCellRequestForSubId(int subId) { + return getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) + .build(); + } + + private NetworkRequest getRouteSelectionRequest(Set netCapsSubIds) { + return getExpectedRequestBase() + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .setSubscriptionIds(netCapsSubIds) + .build(); + } + + private NetworkRequest getTestNetworkRequest(Set netCapsSubIds) { + return new NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST) + .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() { + mUnderlyingNetworkController.teardown(); + + // Expect 5 NetworkBringupCallbacks to be unregistered: 1 for WiFi, 2 for Cellular (1x for + // each subId), and 1 for each of the Wifi signal strength thresholds + verify(mConnectivityManager, times(5)) + .unregisterNetworkCallback(any(NetworkBringupCallback.class)); + verify(mConnectivityManager) + .unregisterNetworkCallback(any(UnderlyingNetworkListener.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 UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() { + return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES); + } + + private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback( + NetworkCapabilities networkCapabilities) { + verify(mConnectivityManager) + .registerNetworkCallback( + eq(getRouteSelectionRequest(INITIAL_SUB_IDS)), + mUnderlyingNetworkListenerCaptor.capture(), + any()); + + UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.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(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + return cb; + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() { + UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + UPDATED_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() { + UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + INITIAL_NETWORK_CAPABILITIES, + UPDATED_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkSuspended() { + UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + SUSPENDED_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't + // change. + cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkResumed() { + UnderlyingNetworkListener 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(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't + // change. + cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForBlocked() { + UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + INITIAL_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + true /* isBlocked */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkLoss() { + UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onLost(mNetwork); + + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null); + } + + @Test + public void testRecordTrackerCallbackIgnoresDuplicateRecord() { + UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + + // Verify no more calls to the UnderlyingNetworkControllerCallback when the + // UnderlyingNetworkRecord does not actually change + verifyNoMoreInteractions(mNetworkControllerCb); + } + + @Test + public void testRecordTrackerCallbackNotifiedAfterTeardown() { + UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); + mUnderlyingNetworkController.teardown(); + + cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); + + // Verify that the only call was during onAvailable() + verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); + } + + // TODO (b/187991063): Add tests for network prioritization +} -- cgit v1.2.3-59-g8ed1b