diff options
-rw-r--r-- | services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java | 53 | ||||
-rw-r--r-- | tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java | 81 |
2 files changed, 100 insertions, 34 deletions
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java index d202a2a60738..5646c752fc90 100644 --- a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java +++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java @@ -30,6 +30,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.CollectionUtils; @@ -94,39 +95,41 @@ public class NetworkStatsSubscriptionsMonitor extends // also needed to track CBRS. final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager); - for (final int subId : newSubs) { - final RatTypeListener match = CollectionUtils.find(mRatListeners, - it -> it.mSubId == subId); - if (match != null) continue; - - // Create listener for every newly added sub. Also store subscriberId into it to - // prevent binder call to telephony when querying RAT. If the subscriberId is empty - // for any reason, such as SIM PIN locked, skip registration. - // SubscriberId will be unavailable again if 1. modem crashed 2. reboot - // 3. re-insert SIM. If that happens, the listeners will be eventually synchronized - // with active sub list once all subscriberIds are ready. - final String subscriberId = mTeleManager.getSubscriberId(subId); - if (TextUtils.isEmpty(subscriberId)) { - Log.d(NetworkStatsService.TAG, "Empty subscriberId for newly added sub " - + subId + ", skip listener registration"); + // IMSI is needed for every newly added sub. Listener stores subscriberId into it to + // prevent binder call to telephony when querying RAT. Keep listener registration with empty + // IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported + // with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration. + final List<Pair<Integer, String>> filteredNewSubs = + CollectionUtils.mapNotNull(newSubs, subId -> { + final String subscriberId = mTeleManager.getSubscriberId(subId); + return TextUtils.isEmpty(subscriberId) ? null : new Pair(subId, subscriberId); + }); + + for (final Pair<Integer, String> sub : filteredNewSubs) { + // Fully match listener with subId and IMSI, since in some rare cases, IMSI might be + // suddenly change regardless of subId, such as switch IMSI feature in modem side. + // If that happens, register new listener with new IMSI and remove old one later. + if (CollectionUtils.find(mRatListeners, + it -> it.equalsKey(sub.first, sub.second)) != null) { continue; } + final RatTypeListener listener = - new RatTypeListener(mExecutor, this, subId, subscriberId); + new RatTypeListener(mExecutor, this, sub.first, sub.second); mRatListeners.add(listener); // Register listener to the telephony manager that associated with specific sub. - mTeleManager.createForSubscriptionId(subId) + mTeleManager.createForSubscriptionId(sub.first) .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE); - Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + subId); + Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + sub.first); } for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) { - // If the new list contains the subId of the listener, keeps it. - final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId); - if (match != null) continue; - - handleRemoveRatTypeListener(listener); + // If there is no subId and IMSI matched the listener, removes it. + if (CollectionUtils.find(filteredNewSubs, + it -> listener.equalsKey(it.first, it.second)) == null) { + handleRemoveRatTypeListener(listener); + } } } @@ -232,5 +235,9 @@ public class NetworkStatsSubscriptionsMonitor extends public int getSubId() { return mSubId; } + + boolean equalsKey(int subId, @NonNull String subscriberId) { + return mSubId == subId && TextUtils.equals(mSubscriberId, subscriberId); + } } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java index 8f093779da11..6d2c7dc39ffd 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -150,7 +151,7 @@ public final class NetworkStatsSubscriptionsMonitorTest { } private void assertRatTypeChangedForSub(String subscriberId, int ratType) { - assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType); + assertEquals(ratType, mMonitor.getRatTypeForSubscriberId(subscriberId)); final ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class); // Verify callback with the subscriberId and the RAT type should be as expected. // It will fail if get a callback with an unexpected RAT type. @@ -302,26 +303,84 @@ public final class NetworkStatsSubscriptionsMonitorTest { reset(mDelegate); // Set IMSI to null again to simulate somehow IMSI is not available, such as - // modem crash. Verify service should not unregister listener. + // modem crash. Verify service should unregister listener. updateSubscriberIdForTestSub(TEST_SUBID1, null); - verify(mTelephonyManager, never()).listen(any(), anyInt()); - assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()), + eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); reset(mDelegate); + clearInvocations(mTelephonyManager); - // Set RAT type of sim1 to LTE. Verify RAT type of sim1 is still changed even if the IMSI - // is not available. The monitor keeps the listener even if the IMSI disappears because - // the IMSI can never change for any given subId, therefore even if the IMSI is updated - // to null, the monitor should continue accepting updates of the RAT type. However, - // telephony is never actually supposed to do this, if the IMSI disappears there should - // not be updates, but it's still the right thing to do theoretically. - setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + // Simulate somehow IMSI is back. Verify service will register with + // another listener and fire callback accordingly. + final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 = + ArgumentCaptor.forClass(RatTypeListener.class); + updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + clearInvocations(mTelephonyManager); + + // Set RAT type of sim1 to LTE. Verify RAT type of sim1 still works. + setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1, TelephonyManager.NETWORK_TYPE_LTE); assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE); reset(mDelegate); mMonitor.stop(); + verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor2.getValue()), + eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + + /** + * Verify that when IMSI suddenly changed for a given subId, the service will register a new + * listener and unregister the old one, and report changes on updated IMSI. This is for modem + * feature that may be enabled for certain carrier, which changes to use a different IMSI while + * roaming on certain networks for multi-IMSI SIM cards, but the subId stays the same. + */ + @Test + public void testSubscriberIdChanged() { + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. + addTestSub(TEST_SUBID1, TEST_IMSI1); + final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + + // Set RAT type of sim1 to UMTS. + // Verify RAT type of sim1 changes accordingly. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); + clearInvocations(mTelephonyManager); + + // Simulate IMSI of sim1 changed to IMSI2. Verify the service will register with + // another listener and remove the old one. The RAT type of new IMSI stays at + // NETWORK_TYPE_UNKNOWN until received initial callback from telephony. + final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 = + ArgumentCaptor.forClass(RatTypeListener.class); + updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI2); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()), eq(PhoneStateListener.LISTEN_NONE)); assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim1 to UMTS for new listener to simulate the initial callback received + // from telephony after registration. Verify RAT type of sim1 changes with IMSI2 + // accordingly. + setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); } } |