diff options
| author | 2021-02-04 18:41:22 +0000 | |
|---|---|---|
| committer | 2021-02-04 18:41:22 +0000 | |
| commit | 4008c80e80c74eb39e6bc7c213de2074deee93a6 (patch) | |
| tree | 4b3cbe122c431a50c3325abb3cc26891eadb56c7 | |
| parent | 958a70d3af698d39f627438d7eed0e6e998d7d46 (diff) | |
| parent | be4020cd097dfd3e7fbff00210e70c470f11682b (diff) | |
Merge changes I03be15d6,Ie1671925,Ie87f4aa3,I24474419,I15bc9aaf am: 6d02108df5 am: be4020cd09
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1569845
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I6ea397badef751567eca2a265688ca883056a7c6
11 files changed, 477 insertions, 106 deletions
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 6a72010738db..916bec27af39 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -290,8 +290,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { public Vcn newVcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, - @NonNull VcnConfig config) { - return new Vcn(vcnContext, subscriptionGroup, config); + @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot) { + return new Vcn(vcnContext, subscriptionGroup, config, snapshot); } /** Gets the subId indicated by the given {@link WifiInfo}. */ @@ -382,6 +383,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // delay) for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final VcnConfig config = mConfigs.get(entry.getKey()); + if (config == null || !snapshot.packageHasPermissionsForSubscriptionGroup( entry.getKey(), config.getProvisioningPackageName())) { @@ -395,10 +397,13 @@ public class VcnManagementService extends IVcnManagementService.Stub { // correct instance is torn down. This could happen as a result of a // Carrier App manually removing/adding a VcnConfig. if (mVcns.get(uuidToTeardown) == instanceToTeardown) { - mVcns.remove(uuidToTeardown).teardownAsynchronously(); + stopVcnLocked(uuidToTeardown); } } }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + } else { + // If this VCN's status has not changed, update it with the new snapshot + entry.getValue().updateSubscriptionSnapshot(mLastSnapshot); } } } @@ -406,14 +411,39 @@ public class VcnManagementService extends IVcnManagementService.Stub { } @GuardedBy("mLock") + private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) { + final Vcn vcnToTeardown = mVcns.remove(uuidToTeardown); + if (vcnToTeardown == null) { + return; + } + + vcnToTeardown.teardownAsynchronously(); + + // Now that the VCN is removed, notify all registered listeners to refresh their + // UnderlyingNetworkPolicy. + notifyAllPolicyListenersLocked(); + } + + @GuardedBy("mLock") + private void notifyAllPolicyListenersLocked() { + for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) { + Binder.withCleanCallingIdentity(() -> policyListener.mListener.onPolicyChanged()); + } + } + + @GuardedBy("mLock") private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup); // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. - final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); + final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot); mVcns.put(subscriptionGroup, newInstance); + + // Now that a new VCN has started, notify all registered listeners to refresh their + // UnderlyingNetworkPolicy. + notifyAllPolicyListenersLocked(); } @GuardedBy("mLock") @@ -476,9 +506,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { synchronized (mLock) { mConfigs.remove(subscriptionGroup); - if (mVcns.containsKey(subscriptionGroup)) { - mVcns.remove(subscriptionGroup).teardownAsynchronously(); - } + stopVcnLocked(subscriptionGroup); writeConfigsToDiskLocked(); } diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index fd12c2d2ebb8..b6ddd93af3b8 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -28,16 +28,15 @@ import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; import android.os.Handler; import android.os.ParcelUuid; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -55,19 +54,18 @@ public class UnderlyingNetworkTracker { @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; + @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities; @NonNull private final UnderlyingNetworkTrackerCallback mCb; @NonNull private final Dependencies mDeps; @NonNull private final Handler mHandler; @NonNull private final ConnectivityManager mConnectivityManager; - @NonNull private final SubscriptionManager mSubscriptionManager; - @NonNull private final SparseArray<NetworkCallback> mCellBringupCallbacks = new SparseArray<>(); + @NonNull private final Map<Integer, NetworkCallback> mCellBringupCallbacks = new ArrayMap<>(); @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback(); @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback(); - @NonNull private final Set<Integer> mSubIds = new ArraySet<>(); - - @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities; + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; + private boolean mIsRunning = true; @Nullable private UnderlyingNetworkRecord mCurrentRecord; @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; @@ -75,11 +73,13 @@ public class UnderlyingNetworkTracker { public UnderlyingNetworkTracker( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities, @NonNull UnderlyingNetworkTrackerCallback cb) { this( vcnContext, subscriptionGroup, + snapshot, requiredUnderlyingNetworkCapabilities, cb, new Dependencies()); @@ -88,11 +88,13 @@ public class UnderlyingNetworkTracker { private UnderlyingNetworkTracker( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities, @NonNull UnderlyingNetworkTrackerCallback cb, @NonNull Dependencies deps) { mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); mRequiredUnderlyingNetworkCapabilities = Objects.requireNonNull( requiredUnderlyingNetworkCapabilities, @@ -103,7 +105,6 @@ public class UnderlyingNetworkTracker { mHandler = new Handler(mVcnContext.getLooper()); mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class); - mSubscriptionManager = mVcnContext.getContext().getSystemService(SubscriptionManager.class); registerNetworkRequests(); } @@ -149,36 +150,49 @@ public class UnderlyingNetworkTracker { private void updateSubIdsAndCellularRequests() { mVcnContext.ensureRunningOnLooperThread(); - Set<Integer> prevSubIds = new ArraySet<>(mSubIds); - mSubIds.clear(); + // Don't bother re-filing NetworkRequests if this Tracker has been torn down. + if (!mIsRunning) { + return; + } - // Ensure NetworkRequests filed for all current subIds in mSubscriptionGroup - // STOPSHIP: b/177364490 use TelephonySubscriptionSnapshot to avoid querying Telephony - List<SubscriptionInfo> subInfos = - mSubscriptionManager.getSubscriptionsInGroup(mSubscriptionGroup); + final Set<Integer> subIdsInSubGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); - for (SubscriptionInfo subInfo : subInfos) { - final int subId = subInfo.getSubscriptionId(); - mSubIds.add(subId); + // new subIds to track = (updated list of subIds) - (currently tracked subIds) + final Set<Integer> subIdsToRegister = new ArraySet<>(subIdsInSubGroup); + subIdsToRegister.removeAll(mCellBringupCallbacks.keySet()); - if (!mCellBringupCallbacks.contains(subId)) { - final NetworkBringupCallback cb = new NetworkBringupCallback(); - mCellBringupCallbacks.put(subId, cb); + // subIds to stop tracking = (currently tracked subIds) - (updated list of subIds) + final Set<Integer> subIdsToUnregister = new ArraySet<>(mCellBringupCallbacks.keySet()); + subIdsToUnregister.removeAll(subIdsInSubGroup); - mConnectivityManager.requestBackgroundNetwork( - getCellNetworkRequestForSubId(subId), mHandler, cb); - } + for (final int subId : subIdsToRegister) { + final NetworkBringupCallback cb = new NetworkBringupCallback(); + mCellBringupCallbacks.put(subId, cb); + + mConnectivityManager.requestBackgroundNetwork( + getCellNetworkRequestForSubId(subId), mHandler, cb); } - // unregister all NetworkCallbacks for outdated subIds - for (final int subId : prevSubIds) { - if (!mSubIds.contains(subId)) { - final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId); - mConnectivityManager.unregisterNetworkCallback(cb); - } + for (final int subId : subIdsToUnregister) { + final NetworkCallback cb = mCellBringupCallbacks.remove(subId); + mConnectivityManager.unregisterNetworkCallback(cb); } } + /** + * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot. + * + * <p>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 snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + mLastSnapshot = snapshot; + updateSubIdsAndCellularRequests(); + } + /** Tears down this Tracker, and releases all underlying network requests. */ public void teardown() { mVcnContext.ensureRunningOnLooperThread(); @@ -186,11 +200,12 @@ public class UnderlyingNetworkTracker { mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback); mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback); - for (final int subId : mSubIds) { - final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId); + for (final NetworkCallback cb : mCellBringupCallbacks.values()) { mConnectivityManager.unregisterNetworkCallback(cb); } - mSubIds.clear(); + mCellBringupCallbacks.clear(); + + mIsRunning = false; } /** Returns whether the currently selected Network matches the given network. */ diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index fd19322cec7f..a82f239948ff 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -27,9 +27,16 @@ import android.os.Message; import android.os.ParcelUuid; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; + +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Represents an single instance of a VCN. @@ -63,6 +70,15 @@ public class Vcn extends Handler { */ private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1; + /** + * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed. + * + * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections. + * + * @param obj TelephonySubscriptionSnapshot + */ + private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2; + /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; @@ -76,20 +92,24 @@ public class Vcn extends Handler { new HashMap<>(); @NonNull private VcnConfig mConfig; + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; private boolean mIsRunning = true; public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, - @NonNull VcnConfig config) { - this(vcnContext, subscriptionGroup, config, new Dependencies()); + @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot) { + this(vcnContext, subscriptionGroup, config, snapshot, new Dependencies()); } - private Vcn( + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull Dependencies deps) { super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; @@ -98,6 +118,7 @@ public class Vcn extends Handler { mRequestListener = new VcnNetworkRequestListener(); mConfig = Objects.requireNonNull(config, "Missing config"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); // Register to receive cached and future NetworkRequests mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); @@ -110,11 +131,24 @@ public class Vcn extends Handler { sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config)); } + /** Asynchronously updates the Subscription snapshot for this VCN. */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot)); + } + /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */ public void teardownAsynchronously() { sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN)); } + /** Get current Gateways for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Set<VcnGatewayConnection> getVcnGatewayConnections() { + return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values())); + } + private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { @@ -137,6 +171,9 @@ public class Vcn extends Handler { case MSG_EVENT_NETWORK_REQUESTED: handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2); break; + case MSG_EVENT_SUBSCRIPTIONS_CHANGED: + handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj); + break; case MSG_CMD_TEARDOWN: handleTeardown(); break; @@ -192,13 +229,26 @@ public class Vcn extends Handler { "Bringing up new VcnGatewayConnection for request " + request.requestId); final VcnGatewayConnection vcnGatewayConnection = - new VcnGatewayConnection( - mVcnContext, mSubscriptionGroup, gatewayConnectionConfig); + mDeps.newVcnGatewayConnection( + mVcnContext, + mSubscriptionGroup, + mLastSnapshot, + gatewayConnectionConfig); mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); } } } + private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { + mLastSnapshot = snapshot; + + if (mIsRunning) { + for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { + gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); + } + } + } + private boolean requestSatisfiedByGatewayConnectionConfig( @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); @@ -221,5 +271,17 @@ public class Vcn extends Handler { return 52; } - private static class Dependencies {} + /** External dependencies used by Vcn, for injection in tests */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class Dependencies { + /** Builds a new VcnGatewayConnection */ + public VcnGatewayConnection newVcnGatewayConnection( + VcnContext vcnContext, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + VcnGatewayConnectionConfig connectionConfig) { + return new VcnGatewayConnection( + vcnContext, subscriptionGroup, snapshot, connectionConfig); + } + } } diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index db372270fee2..853bb4324f90 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -61,10 +61,12 @@ import android.os.ParcelUuid; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; @@ -371,6 +373,16 @@ public class VcnGatewayConnection extends StateMachine { */ private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8; + /** + * Sent when this VcnGatewayConnection is notified of a change in TelephonySubscriptions. + * + * <p>Relevant in all states. + * + * @param arg1 The "all" token; this signal is always honored. + */ + // TODO(b/178426520): implement handling of this event + private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9; + @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull final DisconnectedState mDisconnectedState = new DisconnectedState(); @@ -391,6 +403,11 @@ public class VcnGatewayConnection extends StateMachine { @NonNull final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); + @NonNull private final Object mLock = new Object(); + + @GuardedBy("mLock") + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker; @@ -469,14 +486,16 @@ public class VcnGatewayConnection extends StateMachine { public VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnGatewayConnectionConfig connectionConfig) { - this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies()); + this(vcnContext, subscriptionGroup, snapshot, connectionConfig, new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnGatewayConnectionConfig connectionConfig, @NonNull Dependencies deps) { super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); @@ -485,12 +504,17 @@ public class VcnGatewayConnection extends StateMachine { mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + synchronized (mLock) { + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); + } + mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); mUnderlyingNetworkTracker = mDeps.newUnderlyingNetworkTracker( mVcnContext, subscriptionGroup, + mLastSnapshot, mConnectionConfig.getAllUnderlyingCapabilities(), mUnderlyingNetworkTrackerCallback); mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); @@ -545,6 +569,25 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkTracker.teardown(); } + /** + * Notify this Gateway that subscriptions have changed. + * + * <p>This snapshot should be used to update any keepalive requests necessary for potential + * underlying Networks in this Gateway's subscription group. + */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + // Vcn is the only user of this method and runs on the same Thread, but lock around + // mLastSnapshot to be technically correct. + synchronized (mLock) { + mLastSnapshot = snapshot; + mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); + } + + sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); + } + private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { @Override public void onSelectedUnderlyingNetworkChanged( @@ -682,7 +725,8 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_TRANSFORM_CREATED: // Fallthrough case EVENT_SETUP_COMPLETED: // Fallthrough case EVENT_DISCONNECT_REQUESTED: // Fallthrough - case EVENT_TEARDOWN_TIMEOUT_EXPIRED: + case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough + case EVENT_SUBSCRIPTIONS_CHANGED: logUnexpectedEvent(msg.what); break; default: @@ -1384,10 +1428,15 @@ public class VcnGatewayConnection extends StateMachine { public UnderlyingNetworkTracker newUnderlyingNetworkTracker( VcnContext vcnContext, ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, Set<Integer> requiredUnderlyingNetworkCapabilities, UnderlyingNetworkTrackerCallback callback) { return new UnderlyingNetworkTracker( - vcnContext, subscriptionGroup, requiredUnderlyingNetworkCapabilities, callback); + vcnContext, + subscriptionGroup, + snapshot, + requiredUnderlyingNetworkCapabilities, + callback); } /** Builds a new IkeSession. */ diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java index b9babae4c6b7..fe4ea303610f 100644 --- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -25,6 +25,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + import java.util.Objects; import java.util.Set; @@ -52,8 +55,13 @@ public class VcnNetworkProvider extends NetworkProvider { super(context, looper, VcnNetworkProvider.class.getSimpleName()); } - // Package-private - void registerListener(@NonNull NetworkRequestListener listener) { + /** + * Registers a NetworkRequestListener with this NetworkProvider. + * + * <p>Upon registering, the provided listener will receive all cached requests. + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void registerListener(@NonNull NetworkRequestListener listener) { mListeners.add(listener); // Send listener all cached requests @@ -62,8 +70,9 @@ public class VcnNetworkProvider extends NetworkProvider { } } - // Package-private - void unregisterListener(@NonNull NetworkRequestListener listener) { + /** Unregisters the specified listener from receiving future NetworkRequests. */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void unregisterListener(@NonNull NetworkRequestListener listener) { mListeners.remove(listener); } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index e7d334ebd490..e32e1e831f83 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -184,7 +184,7 @@ public class VcnManagementServiceTest { doAnswer((invocation) -> { // Mock-within a doAnswer is safe, because it doesn't actually run nested. return mock(Vcn.class); - }).when(mMockDeps).newVcn(any(), any(), any()); + }).when(mMockDeps).newVcn(any(), any(), any(), any()); final PersistableBundle bundle = PersistableBundleUtils.fromMap( @@ -304,14 +304,17 @@ public class VcnManagementServiceTest { @Test public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception { - triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); - verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG)); + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + verify(mMockDeps) + .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot)); } @Test public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception { final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet()); @@ -319,6 +322,7 @@ public class VcnManagementServiceTest { mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); + verify(mMockPolicyListener).onPolicyChanged(); } @Test @@ -389,6 +393,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { + verify(mMockPolicyListener, never()).onPolicyChanged(); } } @@ -400,6 +405,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { + verify(mMockPolicyListener, never()).onPolicyChanged(); } } @@ -409,6 +415,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage"); fail("Expected exception due to mismatched packages in config and method call"); } catch (IllegalArgumentException expected) { + verify(mMockPolicyListener, never()).onPolicyChanged(); } } @@ -473,7 +480,12 @@ public class VcnManagementServiceTest { verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); // Verify Vcn is started - verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG)); + verify(mMockDeps) + .newVcn( + eq(mVcnContext), + eq(TEST_UUID_2), + eq(TEST_VCN_CONFIG), + eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT)); // Verify Vcn is updated if it was previously started mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); @@ -520,7 +532,7 @@ public class VcnManagementServiceTest { Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup)); } - private void verifyMergedNetworkCapabilities( + private void verifyMergedNetworkCapabilitiesIsVcnManaged( NetworkCapabilities mergedCapabilities, @Transport int transportType) { assertTrue(mergedCapabilities.hasTransport(transportType)); assertFalse( @@ -529,7 +541,7 @@ public class VcnManagementServiceTest { } @Test - public void testGetUnderlyingNetworkPolicyTransportCell() throws Exception { + public void testGetUnderlyingNetworkPolicyCellular() throws Exception { setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); NetworkCapabilities nc = @@ -542,12 +554,12 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilities( + verifyMergedNetworkCapabilitiesIsVcnManaged( policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR); } @Test - public void testGetUnderlyingNetworkPolicyTransportWifi() throws Exception { + public void testGetUnderlyingNetworkPolicyWifi() throws Exception { setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); WifiInfo wifiInfo = mock(WifiInfo.class); @@ -563,7 +575,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilities( + verifyMergedNetworkCapabilitiesIsVcnManaged( policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI); } @@ -591,4 +603,35 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.getUnderlyingNetworkPolicy(new NetworkCapabilities(), new LinkProperties()); } + + @Test + public void testSubscriptionSnapshotUpdateNotifiesVcn() { + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns(); + final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2); + + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2)); + + verify(vcnInstance).updateSubscriptionSnapshot(eq(snapshot)); + } + + @Test + public void testAddNewVcnUpdatesPolicyListener() throws Exception { + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + + verify(mMockPolicyListener).onPolicyChanged(); + } + + @Test + public void testRemoveVcnUpdatesPolicyListener() throws Exception { + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + + verify(mMockPolicyListener).onPolicyChanged(); + } } diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java index 48e068d14182..1d459a347526 100644 --- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java @@ -42,8 +42,9 @@ import android.net.TelephonyNetworkSpecifier; import android.os.ParcelUuid; import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; +import android.util.ArraySet; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback; import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; @@ -58,13 +59,19 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.Collections; -import java.util.List; +import java.util.Set; import java.util.UUID; public class UnderlyingNetworkTrackerTest { private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final int INITIAL_SUB_ID_1 = 1; private static final int INITIAL_SUB_ID_2 = 2; + private static final int UPDATED_SUB_ID = 3; + + private static final Set<Integer> INITIAL_SUB_IDS = + new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2)); + private static final Set<Integer> UPDATED_SUB_IDS = + new ArraySet<>(Arrays.asList(UPDATED_SUB_ID)); private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES = new NetworkCapabilities.Builder() @@ -90,7 +97,7 @@ public class UnderlyingNetworkTrackerTest { @Mock private Context mContext; @Mock private VcnNetworkProvider mVcnNetworkProvider; @Mock private ConnectivityManager mConnectivityManager; - @Mock private SubscriptionManager mSubscriptionManager; + @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb; @Mock private Network mNetwork; @@ -113,23 +120,14 @@ public class UnderlyingNetworkTrackerTest { mConnectivityManager, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); - setupSystemService( - mContext, - mSubscriptionManager, - Context.TELEPHONY_SUBSCRIPTION_SERVICE, - SubscriptionManager.class); - List<SubscriptionInfo> initialSubInfos = - Arrays.asList( - getSubscriptionInfoForSubId(INITIAL_SUB_ID_1), - getSubscriptionInfoForSubId(INITIAL_SUB_ID_2)); - when(mSubscriptionManager.getSubscriptionsInGroup(eq(SUB_GROUP))) - .thenReturn(initialSubInfos); + when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS); mUnderlyingNetworkTracker = new UnderlyingNetworkTracker( mVcnContext, SUB_GROUP, + mSubscriptionSnapshot, Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET), mNetworkTrackerCb); } @@ -154,23 +152,45 @@ public class UnderlyingNetworkTrackerTest { eq(getWifiRequest()), any(), any(NetworkBringupCallback.class)); - verify(mConnectivityManager) - .requestBackgroundNetwork( - eq(getCellRequestForSubId(INITIAL_SUB_ID_1)), - any(), - any(NetworkBringupCallback.class)); - verify(mConnectivityManager) - .requestBackgroundNetwork( - eq(getCellRequestForSubId(INITIAL_SUB_ID_2)), - any(), - any(NetworkBringupCallback.class)); + verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, INITIAL_SUB_IDS); + verify(mConnectivityManager) .requestBackgroundNetwork( eq(getRouteSelectionRequest()), any(), any(RouteSelectionCallback.class)); + } + + private void verifyBackgroundCellRequests( + TelephonySubscriptionSnapshot snapshot, + ParcelUuid subGroup, + Set<Integer> expectedSubIds) { + verify(snapshot).getAllSubIdsInGroup(eq(subGroup)); + + for (final int subId : expectedSubIds) { + verify(mConnectivityManager) + .requestBackgroundNetwork( + eq(getCellRequestForSubId(subId)), + any(), + any(NetworkBringupCallback.class)); + } + } - verify(mSubscriptionManager).getSubscriptionsInGroup(eq(SUB_GROUP)); + @Test + public void testUpdateSubscriptionSnapshot() { + // Verify initial cell background requests filed + verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, 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 + verify(mConnectivityManager, times(INITIAL_SUB_IDS.size())) + .unregisterNetworkCallback(any(NetworkBringupCallback.class)); + verifyBackgroundCellRequests(subscriptionUpdate, SUB_GROUP, UPDATED_SUB_IDS); } private NetworkRequest getWifiRequest() { diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 4ecd21503165..fbaae6f534a9 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -44,7 +44,8 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testEnterWhileNotRunningTriggersQuit() throws Exception { final VcnGatewayConnection vgc = - new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + new VcnGatewayConnection( + mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps); vgc.setIsRunning(false); vgc.transitionTo(vgc.mDisconnectedState); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index f4ac86d73655..bc6bee28d14f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -24,10 +24,10 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; -import android.annotation.NonNull; -import android.content.Context; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -37,14 +37,15 @@ import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.os.ParcelUuid; import android.os.Process; -import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; 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 org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,8 +57,9 @@ import java.util.UUID; /** Tests for TelephonySubscriptionTracker */ @RunWith(AndroidJUnit4.class) @SmallTest -public class VcnGatewayConnectionTest { +public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { private static final int TEST_UID = Process.myUid(); + private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); private static final int TEST_SIM_SLOT_INDEX = 1; private static final int TEST_SUBSCRIPTION_ID_1 = 2; @@ -73,17 +75,12 @@ public class VcnGatewayConnectionTest { TEST_SUBID_TO_GROUP_MAP = Collections.unmodifiableMap(subIdToGroupMap); } - @NonNull private final Context mContext; - @NonNull private final TestLooper mTestLooper; - @NonNull private final VcnNetworkProvider mVcnNetworkProvider; - @NonNull private final VcnGatewayConnection.Dependencies mDeps; - @NonNull private final WifiInfo mWifiInfo; - - public VcnGatewayConnectionTest() { - mContext = mock(Context.class); - mTestLooper = new TestLooper(); - mVcnNetworkProvider = mock(VcnNetworkProvider.class); - mDeps = mock(VcnGatewayConnection.Dependencies.class); + private WifiInfo mWifiInfo; + + @Before + public void setUp() throws Exception { + super.setUp(); + mWifiInfo = mock(WifiInfo.class); } @@ -132,4 +129,13 @@ public class VcnGatewayConnectionTest { public void testBuildNetworkCapabilitiesUnderlyingCell() throws Exception { verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR); } + + @Test + public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() { + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot); + + verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot)); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 873078036a69..df1341cce20f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -42,10 +42,12 @@ import android.os.ParcelUuid; import android.os.test.TestLooper; import com.android.server.IpSecService; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import org.junit.Before; import org.mockito.ArgumentCaptor; +import java.util.Collections; import java.util.UUID; public class VcnGatewayConnectionTestBase { @@ -54,6 +56,7 @@ public class VcnGatewayConnectionTestBase { protected static final int TEST_IPSEC_SPI_RESOURCE_ID = 1; protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2; protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3; + protected static final int TEST_SUB_ID = 5; protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = new UnderlyingNetworkRecord( @@ -68,6 +71,10 @@ public class VcnGatewayConnectionTestBase { new LinkProperties(), false /* blocked */); + protected static final TelephonySubscriptionSnapshot TEST_SUBSCRIPTION_SNAPSHOT = + new TelephonySubscriptionSnapshot( + Collections.singletonMap(TEST_SUB_ID, TEST_SUB_GRP), Collections.EMPTY_MAP); + @NonNull protected final Context mContext; @NonNull protected final TestLooper mTestLooper; @NonNull protected final VcnNetworkProvider mVcnNetworkProvider; @@ -99,7 +106,7 @@ public class VcnGatewayConnectionTestBase { doReturn(mUnderlyingNetworkTracker) .when(mDeps) - .newUnderlyingNetworkTracker(any(), any(), any(), any()); + .newUnderlyingNetworkTracker(any(), any(), any(), any(), any()); } @Before @@ -114,7 +121,9 @@ public class VcnGatewayConnectionTestBase { mMockIkeSession = mock(VcnIkeSession.class); doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any()); - mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + mGatewayConnection = + new VcnGatewayConnection( + mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps); } protected IpSecTransform makeDummyIpSecTransform() throws Exception { diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java new file mode 100644 index 000000000000..0c1df763a08e --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -0,0 +1,129 @@ +/* + * 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 org.junit.Assert.assertFalse; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.NetworkRequest; +import android.net.vcn.VcnConfig; +import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.os.ParcelUuid; +import android.os.test.TestLooper; + +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Set; +import java.util.UUID; + +public class VcnTest { + private static final String PKG_NAME = VcnTest.class.getPackage().getName(); + private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0)); + private static final int NETWORK_SCORE = 0; + private static final int PROVIDER_ID = 5; + + private Context mContext; + private VcnContext mVcnContext; + private TelephonySubscriptionSnapshot mSubscriptionSnapshot; + private VcnNetworkProvider mVcnNetworkProvider; + private Vcn.Dependencies mDeps; + + private TestLooper mTestLooper; + private VcnConfig mConfig; + private Vcn mVcn; + + @Before + public void setUp() { + mContext = mock(Context.class); + mVcnContext = mock(VcnContext.class); + mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class); + mVcnNetworkProvider = mock(VcnNetworkProvider.class); + mDeps = mock(Vcn.Dependencies.class); + + mTestLooper = new TestLooper(); + + doReturn(PKG_NAME).when(mContext).getOpPackageName(); + doReturn(mContext).when(mVcnContext).getContext(); + doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); + doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); + + // Setup VcnGatewayConnection instance generation + doAnswer((invocation) -> { + // Mock-within a doAnswer is safe, because it doesn't actually run nested. + return mock(VcnGatewayConnection.class); + }).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any()); + + mConfig = + new VcnConfig.Builder(mContext) + .addGatewayConnectionConfig( + VcnGatewayConnectionConfigTest.buildTestConfig()) + .build(); + + mVcn = new Vcn(mVcnContext, TEST_SUB_GROUP, mConfig, mSubscriptionSnapshot, mDeps); + } + + private NetworkRequestListener verifyAndGetRequestListener() { + ArgumentCaptor<NetworkRequestListener> mNetworkRequestListenerCaptor = + ArgumentCaptor.forClass(NetworkRequestListener.class); + verify(mVcnNetworkProvider).registerListener(mNetworkRequestListenerCaptor.capture()); + + return mNetworkRequestListenerCaptor.getValue(); + } + + private NetworkRequest getNetworkRequestWithCapabilities(int[] networkCapabilities) { + final NetworkRequest.Builder builder = new NetworkRequest.Builder(); + for (final int netCapability : networkCapabilities) { + builder.addCapability(netCapability); + } + return builder.build(); + } + + @Test + public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + + requestListener.onNetworkRequested( + getNetworkRequestWithCapabilities(VcnGatewayConnectionConfigTest.EXPOSED_CAPS), + NETWORK_SCORE, + PROVIDER_ID); + mTestLooper.dispatchAll(); + + final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); + assertFalse(gatewayConnections.isEmpty()); + + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + + mVcn.updateSubscriptionSnapshot(updatedSnapshot); + mTestLooper.dispatchAll(); + + for (final VcnGatewayConnection gateway : gatewayConnections) { + verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot)); + } + } +} |