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

Commit 6153723d authored by Benedict Wong's avatar Benedict Wong
Browse files

Add TelephonySubscriptionTracker to VcnMgmtSvc

This changes adds the TelephonySubscriptionTracker to
VcnManagementService, ensuring that VCN Instances are only ever started
once a relevant Subscription has been loaded in Telephony

Bug: 163430955
Test: New VcnManagementService tests added, passing
Change-Id: Ia9396d66b41fadc0a5ed7aa66306e6223a2d29b8
parent 1fe26df5
Loading
Loading
Loading
Loading
+95 −3
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server;

import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;

import static java.util.Objects.requireNonNull;

import android.annotation.NonNull;
@@ -43,6 +46,7 @@ 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.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.util.PersistableBundleUtils;
@@ -53,6 +57,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
@@ -117,6 +122,9 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml";

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);

    /* Binder context for this service */
    @NonNull private final Context mContext;
    @NonNull private final Dependencies mDeps;
@@ -124,6 +132,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @NonNull private final Looper mLooper;
    @NonNull private final Handler mHandler;
    @NonNull private final VcnNetworkProvider mNetworkProvider;
    @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
    @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
    @NonNull private final VcnContext mVcnContext;

    @GuardedBy("mLock")
@@ -134,6 +144,11 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @NonNull
    private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();

    @GuardedBy("mLock")
    @NonNull
    private TelephonySubscriptionSnapshot mLastSnapshot =
            TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;

    @NonNull private final Object mLock = new Object();

    @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
@@ -146,6 +161,9 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        mLooper = mDeps.getLooper();
        mHandler = new Handler(mLooper);
        mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
        mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
        mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
                mContext, mLooper, mTelephonySubscriptionTrackerCb);

        mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
        mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider);
@@ -182,7 +200,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                            mConfigs.put(entry.getKey(), entry.getValue());
                        }
                    }
                    // TODO: Trigger re-evaluation of active VCNs; start/stop VCNs as needed.

                    // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
                    // snapshot, and therefore safe even before telephony subscriptions are loaded.
                    mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
                }
            }
        });
@@ -211,6 +232,14 @@ public class VcnManagementService extends IVcnManagementService.Stub {
            return mHandlerThread.getLooper();
        }

        /** Creates a new VcnInstance using the provided configuration */
        public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
                @NonNull Context context,
                @NonNull Looper looper,
                @NonNull TelephonySubscriptionTrackerCallback callback) {
            return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
        }

        /**
         * Retrieves the caller's UID
         *
@@ -255,6 +284,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    public void systemReady() {
        mContext.getSystemService(ConnectivityManager.class)
                .registerNetworkProvider(mNetworkProvider);
        mTelephonySubscriptionTracker.register();
    }

    private void enforcePrimaryUser() {
@@ -301,14 +331,74 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                "Carrier privilege required for subscription group to set VCN Config");
    }

    private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
        /**
         * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
         *
         * <p>Start any unstarted VCN instances
         *
         * @hide
         */
        public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
            // Startup VCN instances
            synchronized (mLock) {
                mLastSnapshot = snapshot;

                // Start any VCN instances as necessary
                for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
                    if (snapshot.getActiveSubscriptionGroups().contains(entry.getKey())) {
                        // TODO: Add checks to ensure provisioning app is currently carrier
                        //       privileged on this subscription
                        if (!mVcns.containsKey(entry.getKey())) {
                            startVcnLocked(entry.getKey(), entry.getValue());
                        }

                        // Cancel any scheduled teardowns for active subscriptions
                        mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey()));
                    }
                }

                // Schedule teardown of any VCN instances that have lost carrier privileges (after a
                // delay)
                for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
                    if (!snapshot.getActiveSubscriptionGroups().contains(entry.getKey())) {
                        final ParcelUuid uuidToTeardown = entry.getKey();
                        final Vcn instanceToTeardown = entry.getValue();

                        mHandler.postDelayed(() -> {
                            synchronized (mLock) {
                                // Guard against case where this is run after a old instance was
                                // torn down, and a new instance was started. Verify to ensure
                                // 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();
                                }
                            }
                        }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
                    }
                }
            }
        }
    }

    @GuardedBy("mLock")
    private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
        Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);

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

    @GuardedBy("mLock")
    private void startOrUpdateVcnLocked(
            @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
        Slog.v(TAG, "Starting or updating VCN config for subGrp: " + subscriptionGroup);

        if (mVcns.containsKey(subscriptionGroup)) {
            mVcns.get(subscriptionGroup).updateConfig(config);
        } else {
            final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
            mVcns.put(subscriptionGroup, newInstance);
            startVcnLocked(subscriptionGroup, config);
        }
    }

@@ -321,6 +411,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
        requireNonNull(config, "config was null");
        Slog.v(TAG, "VCN config updated for subGrp: " + subscriptionGroup);

        enforceCallingUserAndCarrierPrivilege(subscriptionGroup);

@@ -342,6 +433,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @Override
    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
        Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);

        enforceCallingUserAndCarrierPrivilege(subscriptionGroup);

+3 −0
Original line number Diff line number Diff line
@@ -233,6 +233,9 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
        private final Map<Integer, ParcelUuid> mSubIdToGroupMap;
        private final Set<ParcelUuid> mActiveGroups;

        public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
                new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptySet());

        @VisibleForTesting(visibility = Visibility.PRIVATE)
        TelephonySubscriptionSnapshot(
                @NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
+109 −1
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server;

import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -25,6 +28,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.content.Context;
@@ -44,16 +48,19 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.VcnManagementService.VcnNetworkProvider;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.util.PersistableBundleUtils;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;

import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/** Tests for {@link VcnManagementService}. */
@@ -66,9 +73,10 @@ public class VcnManagementServiceTest {
    private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP =
            Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));

    private static final int TEST_SUBSCRIPTION_ID = 1;
    private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
            new SubscriptionInfo(
                    1 /* id */,
                    TEST_SUBSCRIPTION_ID /* id */,
                    "" /* iccId */,
                    0 /* simSlotIndex */,
                    "Carrier" /* displayName */,
@@ -99,6 +107,8 @@ public class VcnManagementServiceTest {
    private final VcnContext mVcnContext = mock(VcnContext.class);
    private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper =
            mock(PersistableBundleUtils.LockingReadWriteHelper.class);
    private final TelephonySubscriptionTracker mSubscriptionTracker =
            mock(TelephonySubscriptionTracker.class);

    private final VcnManagementService mVcnMgmtSvc;

@@ -116,6 +126,12 @@ public class VcnManagementServiceTest {
                        eq(mMockContext),
                        eq(mTestLooper.getLooper()),
                        any(VcnNetworkProvider.class));
        doReturn(mSubscriptionTracker)
                .when(mMockDeps)
                .newTelephonySubscriptionTracker(
                        eq(mMockContext),
                        eq(mTestLooper.getLooper()),
                        any(TelephonySubscriptionTrackerCallback.class));
        doReturn(mConfigReadWriteHelper)
                .when(mMockDeps)
                .newPersistableBundleLockingReadWriteHelper(any());
@@ -135,6 +151,9 @@ public class VcnManagementServiceTest {

        setupMockedCarrierPrivilege(true);
        mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);

        // Make sure the profiles are loaded.
        mTestLooper.dispatchAll();
    }

    private void setupSystemService(Object service, String name, Class<?> serviceClass) {
@@ -157,6 +176,7 @@ public class VcnManagementServiceTest {

        verify(mConnMgr)
                .registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class));
        verify(mSubscriptionTracker).register();
    }

    @Test
@@ -189,6 +209,94 @@ public class VcnManagementServiceTest {
        verify(mConfigReadWriteHelper).readFromDisk();
    }

    private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
        final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
        doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();

        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
        cb.onNewSnapshot(snapshot);
    }

    private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
        final ArgumentCaptor<TelephonySubscriptionTrackerCallback> captor =
                ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class);
        verify(mMockDeps)
                .newTelephonySubscriptionTracker(
                        eq(mMockContext), eq(mTestLooper.getLooper()), captor.capture());
        return captor.getValue();
    }

    private Vcn startAndGetVcnInstance(ParcelUuid uuid) {
        mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG);
        return mVcnMgmtSvc.getAllVcns().get(uuid);
    }

    @Test
    public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
        verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
    }

    @Test
    public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception {
        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);

        triggerSubscriptionTrackerCallback(Collections.emptySet());

        // Verify teardown after delay
        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
        mTestLooper.dispatchAll();
        verify(vcn).teardownAsynchronously();
    }

    @Test
    public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
            throws Exception {
        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);

        // Simulate SIM unloaded
        triggerSubscriptionTrackerCallback(Collections.emptySet());

        // Simulate new SIM loaded right during teardown delay.
        mTestLooper.moveTimeForward(
                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
        mTestLooper.dispatchAll();
        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2));

        // Verify that even after the full timeout duration, the VCN instance is not torn down
        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
        mTestLooper.dispatchAll();
        verify(vcn, never()).teardownAsynchronously();
    }

    @Test
    public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception {
        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
        final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);

        // Simulate SIM unloaded
        triggerSubscriptionTrackerCallback(Collections.emptySet());

        // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
        // vcnInstance.
        mTestLooper.moveTimeForward(
                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS - 1);
        mTestLooper.dispatchAll();
        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
        final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);

        // Verify that new instance was different, and the old one was torn down
        assertTrue(oldInstance != newInstance);
        verify(oldInstance).teardownAsynchronously();

        // Verify that even after the full timeout duration, the new VCN instance is not torn down
        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
        mTestLooper.dispatchAll();
        verify(newInstance, never()).teardownAsynchronously();
    }

    @Test
    public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
        doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();