Loading src/java/com/android/internal/telephony/CellularNetworkValidator.java +134 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading @@ -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); }; Loading Loading @@ -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); } Loading @@ -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); } } Loading tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java +242 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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(); } Loading Loading @@ -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; } } Loading
src/java/com/android/internal/telephony/CellularNetworkValidator.java +134 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading @@ -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); }; Loading Loading @@ -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); } Loading @@ -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); } } Loading
tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java +242 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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(); } Loading Loading @@ -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; } }