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

Commit 4d145841 authored by Malcolm Chen's avatar Malcolm Chen
Browse files

Skip network validation if validated recently upon switching

Have a fixed sized cache of validated networks in ValidatedNetworkCache.

Bug: 140070796
Test: unittest
Change-Id: I5f7f3880d02afcb70f9b591bdc02e818488989a1
Merged-In: I5f7f3880d02afcb70f9b591bdc02e818488989a1
parent 002f9955
Loading
Loading
Loading
Loading
+134 −5
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.internal.telephony;

import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
@@ -23,6 +26,10 @@ import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.telephony.CellIdentity;
import android.telephony.CellIdentityLte;
import android.telephony.CellInfo;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;

@@ -30,6 +37,12 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.TimeUnit;

/**
 * This class will validate whether cellular network verified by Connectivity's
 * validation process. It listens request on a specific subId, sends a network request
@@ -50,6 +63,8 @@ public class CellularNetworkValidator {

    // Singleton instance.
    private static CellularNetworkValidator sInstance;
    @VisibleForTesting
    public static long mValidationCacheTtl = TimeUnit.DAYS.toMillis(1);

    private int mState = STATE_IDLE;
    private int mSubId;
@@ -66,6 +81,107 @@ public class CellularNetworkValidator {
    public ConnectivityNetworkCallback mNetworkCallback;
    @VisibleForTesting
    public Runnable mTimeoutCallback;
    private final ValidatedNetworkCache mValidatedNetworkCache = new ValidatedNetworkCache();

    private static class ValidatedNetworkCache {
        // A cache with fixed size. It remembers 10 most recently successfully validated networks.
        private static final int VALIDATED_NETWORK_CACHE_SIZE = 10;
        private final PriorityQueue<ValidatedNetwork> mValidatedNetworkPQ =
                new PriorityQueue((Comparator<ValidatedNetwork>) (n1, n2) -> {
                    if (n1.mValidationTimeStamp < n2.mValidationTimeStamp) {
                        return -1;
                    } else if (n1.mValidationTimeStamp > n2.mValidationTimeStamp) {
                        return 1;
                    } else {
                        return 0;
                    }
                });
        private final Map<String, ValidatedNetwork> mValidatedNetworkMap = new HashMap();

        private static final class ValidatedNetwork {
            ValidatedNetwork(String identity, long timeStamp) {
                mValidationIdentity = identity;
                mValidationTimeStamp = timeStamp;
            }
            void update(long timeStamp) {
                mValidationTimeStamp = timeStamp;
            }
            final String mValidationIdentity;
            long mValidationTimeStamp;
        }

        boolean isRecentlyValidated(int subId) {
            long cacheTtl = getValidationCacheTtl();
            String networkIdentity = getValidationNetworkIdentity(subId);
            if (networkIdentity == null || !mValidatedNetworkMap.containsKey(networkIdentity)) {
                return false;
            }
            long validatedTime = mValidatedNetworkMap.get(networkIdentity).mValidationTimeStamp;
            boolean recentlyValidated = System.currentTimeMillis() - validatedTime < cacheTtl;
            logd("isRecentlyValidated on subId " + subId + " ? " + recentlyValidated);
            return recentlyValidated;
        }

        void storeLastValidationResult(int subId, boolean validated) {
            String networkIdentity = getValidationNetworkIdentity(subId);
            logd("storeLastValidationResult for subId " + subId
                    + (validated ? " validated." : " not validated."));
            if (networkIdentity == null) return;

            if (!validated) {
                // If validation failed, clear it from the cache.
                mValidatedNetworkPQ.remove(mValidatedNetworkMap.get(networkIdentity));
                mValidatedNetworkMap.remove(networkIdentity);
                return;
            }
            long time =  System.currentTimeMillis();
            ValidatedNetwork network = mValidatedNetworkMap.get(networkIdentity);
            if (network != null) {
                // Already existed in cache, update.
                network.update(time);
                // Re-add to re-sort.
                mValidatedNetworkPQ.remove(network);
                mValidatedNetworkPQ.add(network);
            } else {
                network = new ValidatedNetwork(networkIdentity, time);
                mValidatedNetworkMap.put(networkIdentity, network);
                mValidatedNetworkPQ.add(network);
            }
            // If exceeded max size, remove the one with smallest validation timestamp.
            if (mValidatedNetworkPQ.size() > VALIDATED_NETWORK_CACHE_SIZE) {
                ValidatedNetwork networkToRemove = mValidatedNetworkPQ.poll();
                mValidatedNetworkMap.remove(networkToRemove.mValidationIdentity);
            }
        }

        private String getValidationNetworkIdentity(int subId) {
            if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
            Phone phone = PhoneFactory.getPhone(SubscriptionController.getInstance()
                    .getPhoneId(subId));
            if (phone == null) return null;

            if (phone.getServiceState() == null) return null;

            NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo(
                    DOMAIN_PS, TRANSPORT_TYPE_WWAN);
            if (regInfo == null || regInfo.getCellIdentity() == null) return null;

            CellIdentity cellIdentity = regInfo.getCellIdentity();
            // TODO: add support for other technologies.
            if (cellIdentity.getType() != CellInfo.TYPE_LTE
                    || cellIdentity.getMccString() == null || cellIdentity.getMncString() == null
                    || ((CellIdentityLte) cellIdentity).getTac() == CellInfo.UNAVAILABLE) {
                return null;
            }

            return cellIdentity.getMccString() + cellIdentity.getMncString() + "_"
                    + ((CellIdentityLte) cellIdentity).getTac() + "_" + subId;
        }

        private long getValidationCacheTtl() {
            return mValidationCacheTtl;
        }
    }

    /**
     * Callback to pass in when starting validation.
@@ -120,8 +236,7 @@ public class CellularNetworkValidator {
        // If it's already validating the same subscription, do nothing.
        if (subId == mSubId) return;

        Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
        if (phone == null) {
        if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
            logd("Failed to start validation. Inactive subId " + subId);
            callback.onValidationResult(false, subId);
            return;
@@ -147,6 +262,8 @@ public class CellularNetworkValidator {

        mTimeoutCallback = () -> {
            logd("timeout on subId " + subId + " validation.");
            // Remember latest validated network.
            mValidatedNetworkCache.storeLastValidationResult(subId, false);
            reportValidationResult(false, subId);
        };

@@ -237,27 +354,37 @@ public class CellularNetworkValidator {
        @Override
        public void onAvailable(Network network) {
            logd("network onAvailable " + network);
            if (ConnectivityNetworkCallback.this.mSubId == CellularNetworkValidator.this.mSubId) {
            if (ConnectivityNetworkCallback.this.mSubId != CellularNetworkValidator.this.mSubId) {
                return;
            }
            TelephonyMetrics.getInstance().writeNetworkValidate(
                    TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
            if (mValidatedNetworkCache.isRecentlyValidated(mSubId)) {
                reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
            }
        }

        @Override
        public void onLosing(Network network, int maxMsToLive) {
            logd("network onLosing " + network + " maxMsToLive " + maxMsToLive);
            mValidatedNetworkCache.storeLastValidationResult(
                    ConnectivityNetworkCallback.this.mSubId, false);
            reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
        }

        @Override
        public void onLost(Network network) {
            logd("network onLost " + network);
            mValidatedNetworkCache.storeLastValidationResult(
                    ConnectivityNetworkCallback.this.mSubId, false);
            reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
        }

        @Override
        public void onUnavailable() {
            logd("onUnavailable");
            mValidatedNetworkCache.storeLastValidationResult(
                    ConnectivityNetworkCallback.this.mSubId, false);
            reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
        }

@@ -266,6 +393,8 @@ public class CellularNetworkValidator {
                NetworkCapabilities networkCapabilities) {
            if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                logd("onValidated");
                mValidatedNetworkCache.storeLastValidationResult(
                        ConnectivityNetworkCallback.this.mSubId, true);
                reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
            }
        }
+242 −2
Original line number Diff line number Diff line
@@ -16,11 +16,15 @@

package com.android.internal.telephony;

import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;

@@ -28,8 +32,11 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
import android.telephony.CellIdentityLte;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneCapability;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -49,6 +56,8 @@ public class CellularNetworkValidatorTest extends TelephonyTest {
            new PhoneCapability(0, 0, 0, 0, 1, 0, null, null, null, null, null, null, null);
    private static final PhoneCapability CAPABILITY_WITHOUT_VALIDATION_SUPPORTED =
            new PhoneCapability(0, 0, 0, 0, 0, 0, null, null, null, null, null, null, null);
    private final CellIdentityLte mCellIdentityLte1 = new CellIdentityLte(123, 456, 0, 0, 111);
    private final CellIdentityLte mCellIdentityLte2 = new CellIdentityLte(321, 654, 0, 0, 222);

    CellularNetworkValidator.ValidationCallback mCallback = (validated, subId) -> {
        mValidated = validated;
@@ -62,6 +71,7 @@ public class CellularNetworkValidatorTest extends TelephonyTest {
        doReturn(CAPABILITY_WITH_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager)
                .getCurrentPhoneCapability();
        mValidatorUT = new CellularNetworkValidator(mContext);
        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
        processAllMessages();
    }

@@ -214,12 +224,242 @@ public class CellularNetworkValidatorTest extends TelephonyTest {
        moveTimeForward(timeout);
        processAllMessages();

        assertFalse(mValidated);
        assertEquals(subId, mValidatedSubId);
        assertValidationResult(subId, false);
    }

    @Test
    @SmallTest
    public void testSkipRecentlyValidatedNetwork() {
        int subId = 1;
        int slotId = 0;
        int timeout = 1000;
        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte1)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());
        testValidateSuccess();

        resetStates();
        mValidatorUT.validate(subId, timeout, true, mCallback);

        assertTrue(mValidatorUT.isValidating());
        assertEquals(subId, mValidatorUT.getSubIdInValidation());

        // As recently validated, onAvailable should trigger switch.
        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));

        assertValidationResult(subId, true);
    }

    @Test
    @SmallTest
    public void testDoNotSkipIfValidationFailed() {
        int subId = 1;
        int slotId = 0;
        int timeout = 1000;
        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte1)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());
        testValidateFailure();

        resetStates();
        mValidatorUT.validate(subId, timeout, true, mCallback);
        assertInValidation(subId);
        // Last time validation fialed, onAvailable should NOT trigger switch.
        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
        assertInValidation(subId);
    }

    @Test
    @SmallTest
    public void testDoNotSkipIfCachExpires() {
        int subId = 1;
        int slotId = 0;
        int timeout = 1000;
        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte1)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());
        testValidateSuccess();

        // Mark mValidationCacheTtl to only 1 second.
        mValidatorUT.mValidationCacheTtl = 1000;
        waitForMs(1100);

        resetStates();
        mValidatorUT.validate(subId, timeout, true, mCallback);
        assertInValidation(subId);

        // Last time validation expired, onAvailable should NOT trigger switch.
        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
        assertInValidation(subId);
    }

    @Test
    @SmallTest
    public void testNetworkCachingOfMultipleSub() {
        int slotId = 0;
        int timeout = 1000;
        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte1)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());

        verifyNetworkRecentlyValidated(1, false);
        verifyNetworkRecentlyValidated(2, false);
        verifyNetworkRecentlyValidated(3, false);
        // Validate sub 1, 2, and 3.
        mValidatorUT.validate(1, timeout, true, mCallback);
        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
        assertValidationResult(1, true);
        verifyNetworkRecentlyValidated(1, true);
        verifyNetworkRecentlyValidated(2, false);
        verifyNetworkRecentlyValidated(3, false);
        mValidatorUT.validate(2, timeout, true, mCallback);
        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
        assertValidationResult(2, true);
        mValidatorUT.validate(3, timeout, true, mCallback);
        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
        assertValidationResult(3, true);
        verifyNetworkRecentlyValidated(1, true);
        verifyNetworkRecentlyValidated(2, true);
        verifyNetworkRecentlyValidated(3, true);

        // When re-validating sub 3, onAvailable should trigger validation callback.
        resetStates();
        mValidatorUT.validate(3, timeout, true, mCallback);
        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
        assertValidationResult(3, true);
        // Mark sub 2 validation failed. Should clear the network from cache.
        resetStates();
        mValidatorUT.validate(2, timeout, true, mCallback);
        mValidatorUT.mNetworkCallback.onLost(new Network(100));
        verifyNetworkRecentlyValidated(2, false);

        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte1)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());
    }

    @Test
    @SmallTest
    public void testNetworkCachingOfMultipleNetworks() {
        int timeout = 1000;
        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte1)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());

        // Validate sub 1.
        verifyNetworkRecentlyValidated(1, false);
        mValidatorUT.validate(1, timeout, true, mCallback);
        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
        verifyNetworkRecentlyValidated(1, true);

        // Change reg state to a different network.
        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte2)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());

        // Should NOT skip validation.
        verifyNetworkRecentlyValidated(1, false);
    }

    @Test
    @SmallTest
    public void testNetworkCachingOverflow() {
        int timeout = 1000;
        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                .setCellIdentity(mCellIdentityLte1)
                .build();
        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                anyInt(), anyInt());

        for (int subId = 8; subId <= 100; subId++) {
            mValidatorUT.validate(subId, timeout, true, mCallback);
            mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
                    .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
            verifyNetworkRecentlyValidated(subId, true);
        }

        // Last 10 subs are kept in cache.
        for (int subId = 1; subId <= 90; subId++) {
            verifyNetworkRecentlyValidated(subId, false);
        }
        // Last 10 subs are kept in cache.
        for (int subId = 91; subId <= 100; subId++) {
            verifyNetworkRecentlyValidated(subId, true);
        }
    }

    private void verifyNetworkRecentlyValidated(int subId, boolean shouldBeRecentlyValidated) {
        // Start validation and send network available callback.
        resetStates();
        mValidatorUT.validate(subId, 1000, true, mCallback);
        mValidatorUT.mNetworkCallback.onAvailable(new Network(1000));

        if (shouldBeRecentlyValidated) {
            assertValidationResult(subId, true);
        } else {
            assertInValidation(subId);
        }

        mValidatorUT.stopValidation();
        resetStates();
    }

    private void assertValidationResult(int subId, boolean shouldPass) {
        // Verify that validation is over.
        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
        assertFalse(mValidatorUT.isValidating());
        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
                mValidatorUT.getSubIdInValidation());

        // Verify result.
        assertEquals(shouldPass, mValidated);
        assertEquals(subId, mValidatedSubId);
    }

    private void assertInValidation(int subId) {
        assertEquals(subId, mValidatorUT.getSubIdInValidation());
        assertTrue(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
        assertTrue(mValidatorUT.isValidating());
    }

    private void resetStates() {
        clearInvocations(mConnectivityManager);
        mValidated = false;
        mValidatedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    }
}