Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 204a2fed authored by Cody Kesting's avatar Cody Kesting
Browse files

Support Safemode for VCNs.

This CL updates VcnManagementService to support Safemode for VCN
instances. Specifically, VcnGatewayConnections will notify their Vcn
instance when they enter Safemode. Vcn instances will in-turn notify
VcnManagementService, which notifies all registered
UnderlyingNetworkPolicyListeners to update their policies.

Bug: 178140973
Test: atest FrameworksVcnTests
Change-Id: I3336c150e9406b3eb2330d2e86cae2ed835730bb
parent 4a3d0555
Loading
Loading
Loading
Loading
+40 −6
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
@@ -291,8 +292,9 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                @NonNull VcnContext vcnContext,
                @NonNull ParcelUuid subscriptionGroup,
                @NonNull VcnConfig config,
                @NonNull TelephonySubscriptionSnapshot snapshot) {
            return new Vcn(vcnContext, subscriptionGroup, config, snapshot);
                @NonNull TelephonySubscriptionSnapshot snapshot,
                @NonNull VcnSafemodeCallback safemodeCallback) {
            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback);
        }

        /** Gets the subId indicated by the given {@link WifiInfo}. */
@@ -438,7 +440,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, mLastSnapshot);
        final VcnSafemodeCallbackImpl safemodeCallback =
                new VcnSafemodeCallbackImpl(subscriptionGroup);

        final Vcn newInstance =
                mDeps.newVcn(
                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback);
        mVcns.put(subscriptionGroup, newInstance);

        // Now that a new VCN has started, notify all registered listeners to refresh their
@@ -536,7 +543,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        }
    }

    /** Get current configuration list for testing purposes */
    /** Get current VCNs for testing purposes */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public Map<ParcelUuid, Vcn> getAllVcns() {
        synchronized (mLock) {
@@ -638,8 +645,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
            synchronized (mLock) {
                ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);

                // TODO(b/178140910): only mark the Network as VCN-managed if not in safe mode
                if (mVcns.containsKey(subGroup)) {
                Vcn vcn = mVcns.get(subGroup);
                if (vcn != null && vcn.isActive()) {
                    isVcnManagedNetwork = true;
                }
            }
@@ -651,4 +658,31 @@ public class VcnManagementService extends IVcnManagementService.Stub {

        return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
    }

    /** Callback for signalling when a Vcn has entered Safemode. */
    public interface VcnSafemodeCallback {
        /** Called by a Vcn to signal that it has entered Safemode. */
        void onEnteredSafemode();
    }

    /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
    private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
        @NonNull private final ParcelUuid mSubGroup;

        private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
        }

        @Override
        public void onEnteredSafemode() {
            synchronized (mLock) {
                // Ignore if this subscription group doesn't exist anymore
                if (!mVcns.containsKey(mSubGroup)) {
                    return;
                }

                notifyAllPolicyListenersLocked();
            }
        }
    }
}
+76 −9
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.VcnManagementService.VcnSafemodeCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;

import java.util.Collections;
@@ -37,6 +38,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Represents an single instance of a VCN.
@@ -82,10 +84,19 @@ public class Vcn extends Handler {
    /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
    private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;

    /**
     * Causes this VCN to immediately enter Safemode.
     *
     * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its
     * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode.
     */
    private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1;

    @NonNull private final VcnContext mVcnContext;
    @NonNull private final ParcelUuid mSubscriptionGroup;
    @NonNull private final Dependencies mDeps;
    @NonNull private final VcnNetworkRequestListener mRequestListener;
    @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback;

    @NonNull
    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
@@ -94,14 +105,33 @@ public class Vcn extends Handler {
    @NonNull private VcnConfig mConfig;
    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;

    private boolean mIsRunning = true;
    /**
     * Whether this Vcn instance is active and running.
     *
     * <p>The value will be {@code true} while running. It will be {@code false} if the VCN has been
     * shut down or has entered safe mode.
     *
     * <p>This AtomicBoolean is required in order to ensure consistency and correctness across
     * multiple threads. Unlike the rest of the Vcn, this is queried synchronously on Binder threads
     * from VcnManagementService, and therefore cannot rely on guarantees of running on the VCN
     * Looper.
     */
    // TODO(b/179429339): update when exiting safemode (when a new VcnConfig is provided)
    private final AtomicBoolean mIsActive = new AtomicBoolean(true);

    public Vcn(
            @NonNull VcnContext vcnContext,
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull VcnConfig config,
            @NonNull TelephonySubscriptionSnapshot snapshot) {
        this(vcnContext, subscriptionGroup, config, snapshot, new Dependencies());
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnSafemodeCallback vcnSafemodeCallback) {
        this(
                vcnContext,
                subscriptionGroup,
                config,
                snapshot,
                vcnSafemodeCallback,
                new Dependencies());
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -110,10 +140,13 @@ public class Vcn extends Handler {
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull VcnConfig config,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnSafemodeCallback vcnSafemodeCallback,
            @NonNull Dependencies deps) {
        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
        mVcnContext = vcnContext;
        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
        mVcnSafemodeCallback =
                Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback");
        mDeps = Objects.requireNonNull(deps, "Missing deps");
        mRequestListener = new VcnNetworkRequestListener();

@@ -143,6 +176,11 @@ public class Vcn extends Handler {
        sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
    }

    /** Synchronously checks whether this Vcn is active. */
    public boolean isActive() {
        return mIsActive.get();
    }

    /** Get current Gateways for testing purposes */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public Set<VcnGatewayConnection> getVcnGatewayConnections() {
@@ -160,7 +198,7 @@ public class Vcn extends Handler {

    @Override
    public void handleMessage(@NonNull Message msg) {
        if (!mIsRunning) {
        if (!isActive()) {
            return;
        }

@@ -177,6 +215,9 @@ public class Vcn extends Handler {
            case MSG_CMD_TEARDOWN:
                handleTeardown();
                break;
            case MSG_CMD_ENTER_SAFEMODE:
                handleEnterSafemode();
                break;
            default:
                Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
        }
@@ -198,7 +239,13 @@ public class Vcn extends Handler {
            gatewayConnection.teardownAsynchronously();
        }

        mIsRunning = false;
        mIsActive.set(false);
    }

    private void handleEnterSafemode() {
        handleTeardown();

        mVcnSafemodeCallback.onEnteredSafemode();
    }

    private void handleNetworkRequested(
@@ -233,7 +280,8 @@ public class Vcn extends Handler {
                                mVcnContext,
                                mSubscriptionGroup,
                                mLastSnapshot,
                                gatewayConnectionConfig);
                                gatewayConnectionConfig,
                                new VcnGatewayStatusCallbackImpl());
                mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
            }
        }
@@ -242,7 +290,7 @@ public class Vcn extends Handler {
    private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
        mLastSnapshot = snapshot;

        if (mIsRunning) {
        if (isActive()) {
            for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
                gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
            }
@@ -271,6 +319,20 @@ public class Vcn extends Handler {
        return 52;
    }

    /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    public interface VcnGatewayStatusCallback {
        /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */
        void onEnteredSafemode();
    }

    private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
        @Override
        public void onEnteredSafemode() {
            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE));
        }
    }

    /** External dependencies used by Vcn, for injection in tests */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    public static class Dependencies {
@@ -279,9 +341,14 @@ public class Vcn extends Handler {
                VcnContext vcnContext,
                ParcelUuid subscriptionGroup,
                TelephonySubscriptionSnapshot snapshot,
                VcnGatewayConnectionConfig connectionConfig) {
                VcnGatewayConnectionConfig connectionConfig,
                VcnGatewayStatusCallback gatewayStatusCallback) {
            return new VcnGatewayConnection(
                    vcnContext, subscriptionGroup, snapshot, connectionConfig);
                    vcnContext,
                    subscriptionGroup,
                    snapshot,
                    connectionConfig,
                    gatewayStatusCallback);
        }
    }
}
+14 −2
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ 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;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;

import java.io.IOException;
import java.net.Inet4Address;
@@ -412,6 +413,7 @@ public class VcnGatewayConnection extends StateMachine {
    @NonNull private final ParcelUuid mSubscriptionGroup;
    @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
    @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
    @NonNull private final Dependencies mDeps;

    @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
@@ -487,8 +489,15 @@ public class VcnGatewayConnection extends StateMachine {
            @NonNull VcnContext vcnContext,
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnGatewayConnectionConfig connectionConfig) {
        this(vcnContext, subscriptionGroup, snapshot, connectionConfig, new Dependencies());
            @NonNull VcnGatewayConnectionConfig connectionConfig,
            @NonNull VcnGatewayStatusCallback gatewayStatusCallback) {
        this(
                vcnContext,
                subscriptionGroup,
                snapshot,
                connectionConfig,
                gatewayStatusCallback,
                new Dependencies());
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -497,11 +506,14 @@ public class VcnGatewayConnection extends StateMachine {
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnGatewayConnectionConfig connectionConfig,
            @NonNull VcnGatewayStatusCallback gatewayStatusCallback,
            @NonNull Dependencies deps) {
        super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
        mVcnContext = vcnContext;
        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
        mGatewayStatusCallback =
                Objects.requireNonNull(gatewayStatusCallback, "Missing gatewayStatusCallback");
        mDeps = Objects.requireNonNull(deps, "Missing deps");

        synchronized (mLock) {
+6 −1
Original line number Diff line number Diff line
@@ -59,12 +59,17 @@ public class VcnGatewayConnectionConfigTest {

    // Public for use in VcnGatewayConnectionTest
    public static VcnGatewayConnectionConfig buildTestConfig() {
        return buildTestConfigWithExposedCaps(EXPOSED_CAPS);
    }

    // Public for use in VcnGatewayConnectionTest
    public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) {
        final VcnGatewayConnectionConfig.Builder builder =
                new VcnGatewayConnectionConfig.Builder()
                        .setRetryInterval(RETRY_INTERVALS_MS)
                        .setMaxMtu(MAX_MTU);

        for (int caps : EXPOSED_CAPS) {
        for (int caps : exposedCaps) {
            builder.addExposedCapability(caps);
        }

+29 −3
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.VcnManagementService.VcnSafemodeCallback;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
@@ -142,6 +143,9 @@ public class VcnManagementServiceTest {
    private final TelephonySubscriptionTracker mSubscriptionTracker =
            mock(TelephonySubscriptionTracker.class);

    private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor =
            ArgumentCaptor.forClass(VcnSafemodeCallback.class);

    private final VcnManagementService mVcnMgmtSvc;

    private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener =
@@ -184,7 +188,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(), any());
        }).when(mMockDeps).newVcn(any(), any(), any(), any(), any());

        final PersistableBundle bundle =
                PersistableBundleUtils.fromMap(
@@ -307,7 +311,7 @@ public class VcnManagementServiceTest {
        TelephonySubscriptionSnapshot snapshot =
                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
        verify(mMockDeps)
                .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot));
                .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), any());
    }

    @Test
@@ -485,7 +489,8 @@ public class VcnManagementServiceTest {
                        eq(mVcnContext),
                        eq(TEST_UUID_2),
                        eq(TEST_VCN_CONFIG),
                        eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT));
                        eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT),
                        any());

        // Verify Vcn is updated if it was previously started
        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
@@ -634,4 +639,25 @@ public class VcnManagementServiceTest {

        verify(mMockPolicyListener).onPolicyChanged();
    }

    @Test
    public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception {
        TelephonySubscriptionSnapshot snapshot =
                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
        verify(mMockDeps)
                .newVcn(
                        eq(mVcnContext),
                        eq(TEST_UUID_1),
                        eq(TEST_VCN_CONFIG),
                        eq(snapshot),
                        mSafemodeCallbackCaptor.capture());

        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);

        VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue();
        safemodeCallback.onEnteredSafemode();

        assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive());
        verify(mMockPolicyListener).onPolicyChanged();
    }
}
Loading