Loading services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java +30 −23 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -94,41 +95,43 @@ 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. // 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); if (TextUtils.isEmpty(subscriberId)) { Log.d(NetworkStatsService.TAG, "Empty subscriberId for newly added sub " + subId + ", skip listener registration"); 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; // 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); } } } @NonNull private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) { Loading Loading @@ -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); } } } tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java +70 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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); } } Loading
services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java +30 −23 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -94,41 +95,43 @@ 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. // 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); if (TextUtils.isEmpty(subscriberId)) { Log.d(NetworkStatsService.TAG, "Empty subscriberId for newly added sub " + subId + ", skip listener registration"); 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; // 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); } } } @NonNull private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) { Loading Loading @@ -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); } } }
tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java +70 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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); } }