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

Commit e60d9a82 authored by junyulai's avatar junyulai
Browse files

Support dynamically update IMSI

Currently, NetworkStatsSubscriptionsMonitor will skip the
listener registration until the IMSI is available to deal
with SIM PIN locked case. However, this solution can only
handle the case that IMSI changes from/to null. And it also
relies on the assumption that IMSI never changes for a subId.

Thus, support dynamically update IMSI to handle IMSI changes
more robustly.

This patch also address leftover comments at ag/12400327.

Test: enable SIM PIN and manually test
Test: atest NetworkStatsSubscriptionsMonitorTest#testSubscriberIdUnavailable
Test: atest NetworkStatsSubscriptionsMonitorTest#testSubscriberIdChanged
Test: ./out/host/linux-x86/bin/statsd_testdrive 10082
Bug: 160941101

Change-Id: I625a5b10ee4806f6fee99c2d9d6c5e7977ff785e
parent fc6fbde5
Loading
Loading
Loading
Loading
+30 −23
Original line number Diff line number Diff line
@@ -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,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) {
@@ -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);
        }
    }
}
+70 −11
Original line number Diff line number Diff line
@@ -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);
    }
}