Merge changes I03be15d6,Ie1671925,Ie87f4aa3,I24474419,I15bc9aaf

* changes:
  Fix test method naming in VcnManagementServiceTest.
  Fix field order for UnderlyingNetworkTracker.
  Notify PolicyListeners to refresh their policy on VCN changes.
  Unit test TelephonySubscriptionSnapshot changes for VCNs.
  Notify UnderlyingNetworkTracker for Subscription changes.
This commit is contained in:
Cody Kesting
2021-02-04 17:14:57 +00:00
committed by Gerrit Code Review
11 changed files with 480 additions and 109 deletions

View File

@@ -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,16 +397,40 @@ 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);
}
}
}
}
}
@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);
@@ -412,8 +438,12 @@ public class VcnManagementService extends IVcnManagementService.Stub {
// 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();
}

View File

@@ -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,34 +150,47 @@ public class UnderlyingNetworkTracker {
private void updateSubIdsAndCellularRequests() {
mVcnContext.ensureRunningOnLooperThread();
Set<Integer> prevSubIds = new ArraySet<>(mSubIds);
mSubIds.clear();
// Ensure NetworkRequests filed for all current subIds in mSubscriptionGroup
// STOPSHIP: b/177364490 use TelephonySubscriptionSnapshot to avoid querying Telephony
List<SubscriptionInfo> subInfos =
mSubscriptionManager.getSubscriptionsInGroup(mSubscriptionGroup);
for (SubscriptionInfo subInfo : subInfos) {
final int subId = subInfo.getSubscriptionId();
mSubIds.add(subId);
if (!mCellBringupCallbacks.contains(subId)) {
final NetworkBringupCallback cb = new NetworkBringupCallback();
mCellBringupCallbacks.put(subId, cb);
mConnectivityManager.requestBackgroundNetwork(
getCellNetworkRequestForSubId(subId), mHandler, cb);
}
// Don't bother re-filing NetworkRequests if this Tracker has been torn down.
if (!mIsRunning) {
return;
}
// unregister all NetworkCallbacks for outdated subIds
for (final int subId : prevSubIds) {
if (!mSubIds.contains(subId)) {
final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
mConnectivityManager.unregisterNetworkCallback(cb);
}
final Set<Integer> subIdsInSubGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
// new subIds to track = (updated list of subIds) - (currently tracked subIds)
final Set<Integer> subIdsToRegister = new ArraySet<>(subIdsInSubGroup);
subIdsToRegister.removeAll(mCellBringupCallbacks.keySet());
// subIds to stop tracking = (currently tracked subIds) - (updated list of subIds)
final Set<Integer> subIdsToUnregister = new ArraySet<>(mCellBringupCallbacks.keySet());
subIdsToUnregister.removeAll(subIdsInSubGroup);
for (final int subId : subIdsToRegister) {
final NetworkBringupCallback cb = new NetworkBringupCallback();
mCellBringupCallbacks.put(subId, cb);
mConnectivityManager.requestBackgroundNetwork(
getCellNetworkRequestForSubId(subId), mHandler, 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. */
@@ -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. */

View File

@@ -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);
}
}
}

View File

@@ -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. */

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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));
}
verify(mSubscriptionManager).getSubscriptionsInGroup(eq(SUB_GROUP));
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));
}
}
@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() {

View File

@@ -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);

View File

@@ -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;
private WifiInfo mWifiInfo;
@Before
public void setUp() throws Exception {
super.setUp();
public VcnGatewayConnectionTest() {
mContext = mock(Context.class);
mTestLooper = new TestLooper();
mVcnNetworkProvider = mock(VcnNetworkProvider.class);
mDeps = mock(VcnGatewayConnection.Dependencies.class);
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));
}
}

View File

@@ -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 {

View File

@@ -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));
}
}
}