diff options
| -rw-r--r-- | services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java | 314 | ||||
| -rw-r--r-- | tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java | 333 |
2 files changed, 647 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java new file mode 100644 index 000000000000..c0608072df9d --- /dev/null +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; +import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX; +import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX; +import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.ParcelUuid; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +/** + * TelephonySubscriptionTracker provides a caching layer for tracking active subscription groups. + * + * <p>This class performs two roles: + * + * <ol> + * <li>De-noises subscription changes by ensuring that only changes in active and ready + * subscription groups are acted upon + * <li>Caches mapping between subIds and subscription groups + * </ol> + * + * <p>An subscription group is active and ready if any of its contained subIds has had BOTH the + * {@link CarrierConfigManager#isConfigForIdentifiedCarrier()} return true, AND the subscription is + * listed as active per SubscriptionManager#getAllSubscriptionInfoList(). + * + * <p>Note that due to the asynchronous nature of callbacks and broadcasts, the output of this class + * is (only) eventually consistent. + * + * @hide + */ +public class TelephonySubscriptionTracker extends BroadcastReceiver { + @NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName(); + private static final boolean LOG_DBG = false; // STOPSHIP if true + + @NonNull private final Context mContext; + @NonNull private final Handler mHandler; + @NonNull private final TelephonySubscriptionTrackerCallback mCallback; + @NonNull private final Dependencies mDeps; + + @NonNull private final SubscriptionManager mSubscriptionManager; + @NonNull private final CarrierConfigManager mCarrierConfigManager; + + // TODO (Android T+): Add ability to handle multiple subIds per slot. + @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>(); + @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener; + + @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot; + + public TelephonySubscriptionTracker( + @NonNull Context context, + @NonNull Handler handler, + @NonNull TelephonySubscriptionTrackerCallback callback) { + this(context, handler, callback, new Dependencies()); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + TelephonySubscriptionTracker( + @NonNull Context context, + @NonNull Handler handler, + @NonNull TelephonySubscriptionTrackerCallback callback, + @NonNull Dependencies deps) { + mContext = Objects.requireNonNull(context, "Missing context"); + mHandler = Objects.requireNonNull(handler, "Missing handler"); + mCallback = Objects.requireNonNull(callback, "Missing callback"); + mDeps = Objects.requireNonNull(deps, "Missing deps"); + + mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); + mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); + + mSubscriptionChangedListener = + new OnSubscriptionsChangedListener() { + @Override + public void onSubscriptionsChanged() { + handleSubscriptionsChanged(); + } + }; + } + + /** Registers the receivers, and starts tracking subscriptions. */ + public void register() { + mContext.registerReceiver( + this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED), null, mHandler); + mSubscriptionManager.addOnSubscriptionsChangedListener( + new HandlerExecutor(mHandler), mSubscriptionChangedListener); + } + + /** Unregisters the receivers, and stops tracking subscriptions. */ + public void unregister() { + mContext.unregisterReceiver(this); + mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener); + } + + /** + * Handles subscription changes, correlating available subscriptions and loaded carrier configs + * + * <p>The subscription change listener is registered with a HandlerExecutor backed by mHandler, + * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking. + */ + public void handleSubscriptionsChanged() { + final Set<ParcelUuid> activeSubGroups = new ArraySet<>(); + final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>(); + + final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList(); + if (allSubs == null) { + return; // Telephony crashed; no way to verify subscriptions. + } + + // If allSubs is empty, no subscriptions exist. Cache will be cleared by virtue of no active + // subscriptions + for (SubscriptionInfo subInfo : allSubs) { + if (subInfo.getGroupUuid() == null) { + continue; + } + + // Build subId -> subGrp cache + newSubIdToGroupMap.put(subInfo.getSubscriptionId(), subInfo.getGroupUuid()); + + // Update subscription groups that are both ready, and active. For a group to be + // considered active, both of the following must be true: + // + // 1. A final CARRIER_CONFIG_CHANGED (where config is for an identified carrier) + // broadcast must have been received for the subId + // 2. A active subscription (is loaded into a SIM slot) must be part of the subscription + // group. + if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX + && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { + activeSubGroups.add(subInfo.getGroupUuid()); + } + } + + final TelephonySubscriptionSnapshot newSnapshot = + new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups); + + // If snapshot was meaningfully updated, fire the callback + if (!newSnapshot.equals(mCurrentSnapshot)) { + mCurrentSnapshot = newSnapshot; + mHandler.post( + () -> { + mCallback.onNewSnapshot(newSnapshot); + }); + } + } + + /** + * Broadcast receiver for ACTION_CARRIER_CONFIG_CHANGED + * + * <p>The broadcast receiver is registered with mHandler, so callbacks & broadcasts are all + * serialized on mHandler, avoiding the need for locking. + */ + @Override + public void onReceive(Context context, Intent intent) { + // Accept sticky broadcasts; if CARRIER_CONFIG_CHANGED was previously broadcast and it + // already was for an identified carrier, we can stop waiting for initial load to complete + if (!ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) { + return; + } + + final int subId = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID); + final int slotId = intent.getIntExtra(EXTRA_SLOT_INDEX, INVALID_SIM_SLOT_INDEX); + + if (slotId == INVALID_SIM_SLOT_INDEX) { + return; + } + + if (SubscriptionManager.isValidSubscriptionId(subId)) { + final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId); + if (mDeps.isConfigForIdentifiedCarrier(carrierConfigs)) { + Slog.v(TAG, String.format("SubId %s ready for SlotId %s", subId, slotId)); + mReadySubIdsBySlotId.put(slotId, subId); + handleSubscriptionsChanged(); + } + } else { + Slog.v(TAG, "Slot unloaded: " + slotId); + mReadySubIdsBySlotId.remove(slotId); + handleSubscriptionsChanged(); + } + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) { + mReadySubIdsBySlotId.putAll(readySubIdsBySlotId); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + Map<Integer, Integer> getReadySubIdsBySlotId() { + return Collections.unmodifiableMap(mReadySubIdsBySlotId); + } + + /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ + public static class TelephonySubscriptionSnapshot { + private final Map<Integer, ParcelUuid> mSubIdToGroupMap; + private final Set<ParcelUuid> mActiveGroups; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + TelephonySubscriptionSnapshot( + @NonNull Map<Integer, ParcelUuid> subIdToGroupMap, + @NonNull Set<ParcelUuid> activeGroups) { + mSubIdToGroupMap = Collections.unmodifiableMap( + Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null")); + mActiveGroups = Collections.unmodifiableSet( + Objects.requireNonNull(activeGroups, "activeGroups was null")); + } + + /** Returns the active subscription groups */ + @NonNull + public Set<ParcelUuid> getActiveSubscriptionGroups() { + return mActiveGroups; + } + + /** Returns the Subscription Group for a given subId. */ + @Nullable + public ParcelUuid getGroupForSubId(int subId) { + return mSubIdToGroupMap.get(subId); + } + + /** + * Returns all the subIds in a given group, including available, but inactive subscriptions. + */ + @NonNull + public Set<Integer> getAllSubIdsInGroup(ParcelUuid subGrp) { + final Set<Integer> subIds = new ArraySet<>(); + + for (Entry<Integer, ParcelUuid> entry : mSubIdToGroupMap.entrySet()) { + if (subGrp.equals(entry.getValue())) { + subIds.add(entry.getKey()); + } + } + + return subIds; + } + + @Override + public int hashCode() { + return Objects.hash(mSubIdToGroupMap, mActiveGroups); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TelephonySubscriptionSnapshot)) { + return false; + } + + final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj; + + return mSubIdToGroupMap.equals(other.mSubIdToGroupMap) + && mActiveGroups.equals(other.mActiveGroups); + } + } + + /** + * Interface for listening to changes in subscriptions + * + * @see TelephonySubscriptionTracker + */ + public interface TelephonySubscriptionTrackerCallback { + /** + * Called when subscription information changes, and a new subscription snapshot was taken + * + * @param snapshot the snapshot of subscription information. + */ + void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot); + } + + /** External static dependencies for test injection */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class Dependencies { + /** Checks if the given bundle is for an identified carrier */ + public boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) { + return CarrierConfigManager.isConfigForIdentifiedCarrier(bundle); + } + } +} diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java new file mode 100644 index 000000000000..17b8f64a13fa --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; +import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX; +import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX; +import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.ParcelUuid; +import android.os.test.TestLooper; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.util.ArraySet; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** Tests for TelephonySubscriptionTracker */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TelephonySubscriptionTrackerTest { + private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); + private static final int TEST_SIM_SLOT_INDEX = 1; + private static final int TEST_SUBSCRIPTION_ID_1 = 2; + private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class); + private static final int TEST_SUBSCRIPTION_ID_2 = 3; + private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class); + private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP; + + static { + final Map<Integer, ParcelUuid> subIdToGroupMap = new HashMap<>(); + subIdToGroupMap.put(TEST_SUBSCRIPTION_ID_1, TEST_PARCEL_UUID); + subIdToGroupMap.put(TEST_SUBSCRIPTION_ID_2, TEST_PARCEL_UUID); + TEST_SUBID_TO_GROUP_MAP = Collections.unmodifiableMap(subIdToGroupMap); + } + + @NonNull private final Context mContext; + @NonNull private final TestLooper mTestLooper; + @NonNull private final Handler mHandler; + @NonNull private final TelephonySubscriptionTracker.Dependencies mDeps; + + @NonNull private final SubscriptionManager mSubscriptionManager; + @NonNull private final CarrierConfigManager mCarrierConfigManager; + + @NonNull private TelephonySubscriptionTrackerCallback mCallback; + @NonNull private TelephonySubscriptionTracker mTelephonySubscriptionTracker; + + public TelephonySubscriptionTrackerTest() { + mContext = mock(Context.class); + mTestLooper = new TestLooper(); + mHandler = new Handler(mTestLooper.getLooper()); + mDeps = mock(TelephonySubscriptionTracker.Dependencies.class); + + mSubscriptionManager = mock(SubscriptionManager.class); + mCarrierConfigManager = mock(CarrierConfigManager.class); + + doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE) + .when(mContext) + .getSystemServiceName(SubscriptionManager.class); + doReturn(mSubscriptionManager) + .when(mContext) + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + + doReturn(Context.CARRIER_CONFIG_SERVICE) + .when(mContext) + .getSystemServiceName(CarrierConfigManager.class); + doReturn(mCarrierConfigManager) + .when(mContext) + .getSystemService(Context.CARRIER_CONFIG_SERVICE); + + // subId 1, 2 are in same subGrp, only subId 1 is active + doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid(); + doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_2).getGroupUuid(); + doReturn(TEST_SIM_SLOT_INDEX).when(TEST_SUBINFO_1).getSimSlotIndex(); + doReturn(INVALID_SIM_SLOT_INDEX).when(TEST_SUBINFO_2).getSimSlotIndex(); + doReturn(TEST_SUBSCRIPTION_ID_1).when(TEST_SUBINFO_1).getSubscriptionId(); + doReturn(TEST_SUBSCRIPTION_ID_2).when(TEST_SUBINFO_2).getSubscriptionId(); + } + + @Before + public void setUp() throws Exception { + mCallback = mock(TelephonySubscriptionTrackerCallback.class); + mTelephonySubscriptionTracker = + new TelephonySubscriptionTracker(mContext, mHandler, mCallback, mDeps); + mTelephonySubscriptionTracker.register(); + + doReturn(true).when(mDeps).isConfigForIdentifiedCarrier(any()); + doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2)) + .when(mSubscriptionManager) + .getAllSubscriptionInfoList(); + } + + private IntentFilter getIntentFilter() { + final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class); + verify(mContext).registerReceiver(any(), captor.capture(), any(), any()); + + return captor.getValue(); + } + + private OnSubscriptionsChangedListener getOnSubscriptionsChangedListener() { + final ArgumentCaptor<OnSubscriptionsChangedListener> captor = + ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class); + verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), captor.capture()); + + return captor.getValue(); + } + + private Intent buildTestBroadcastIntent(boolean hasValidSubscription) { + Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED); + intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX); + intent.putExtra( + EXTRA_SUBSCRIPTION_INDEX, + hasValidSubscription ? TEST_SUBSCRIPTION_ID_1 : INVALID_SUBSCRIPTION_ID); + + return intent; + } + + private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) { + return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups); + } + + private TelephonySubscriptionSnapshot buildExpectedSnapshot( + Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) { + return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups); + } + + private void verifyNoActiveSubscriptions() { + verify(mCallback).onNewSnapshot( + argThat(snapshot -> snapshot.getActiveSubscriptionGroups().isEmpty())); + } + + private void setupReadySubIds() { + mTelephonySubscriptionTracker.setReadySubIdsBySlotId( + Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1)); + } + + @Test + public void testRegister() throws Exception { + verify(mContext) + .registerReceiver( + eq(mTelephonySubscriptionTracker), + any(IntentFilter.class), + any(), + eq(mHandler)); + final IntentFilter filter = getIntentFilter(); + assertEquals(1, filter.countActions()); + assertTrue(filter.hasAction(ACTION_CARRIER_CONFIG_CHANGED)); + + verify(mSubscriptionManager) + .addOnSubscriptionsChangedListener(any(HandlerExecutor.class), any()); + assertNotNull(getOnSubscriptionsChangedListener()); + } + + @Test + public void testUnregister() throws Exception { + mTelephonySubscriptionTracker.unregister(); + + verify(mContext).unregisterReceiver(eq(mTelephonySubscriptionTracker)); + + final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); + verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(eq(listener)); + } + + @Test + public void testOnSubscriptionsChangedFired_NoReadySubIds() throws Exception { + final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + verifyNoActiveSubscriptions(); + } + + @Test + public void testOnSubscriptionsChangedFired_WithReadySubIds() throws Exception { + setupReadySubIds(); + + final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); + listener.onSubscriptionsChanged(); + mTestLooper.dispatchAll(); + + final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + } + + @Test + public void testReceiveBroadcast_ConfigReadyWithSubscriptions() throws Exception { + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + mTestLooper.dispatchAll(); + + final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + } + + @Test + public void testReceiveBroadcast_ConfigReadyNoSubscriptions() throws Exception { + doReturn(new ArrayList<SubscriptionInfo>()) + .when(mSubscriptionManager) + .getAllSubscriptionInfoList(); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + mTestLooper.dispatchAll(); + + // Expect an empty snapshot + verify(mCallback).onNewSnapshot( + eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet()))); + } + + @Test + public void testReceiveBroadcast_SlotCleared() throws Exception { + setupReadySubIds(); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); + mTestLooper.dispatchAll(); + + verifyNoActiveSubscriptions(); + assertTrue(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().isEmpty()); + } + + @Test + public void testReceiveBroadcast_ConfigNotReady() throws Exception { + doReturn(false).when(mDeps).isConfigForIdentifiedCarrier(any()); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + mTestLooper.dispatchAll(); + + // No interactions expected; config was not loaded + verifyNoMoreInteractions(mCallback); + } + + @Test + public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception { + final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + mTestLooper.dispatchAll(); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + assertNotNull( + mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); + + doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList(); + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + mTestLooper.dispatchAll(); + verify(mCallback).onNewSnapshot( + eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet()))); + } + + @Test + public void testSlotClearedAfterValidTriggersCallbacks() throws Exception { + final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + mTestLooper.dispatchAll(); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups))); + assertNotNull( + mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); + mTestLooper.dispatchAll(); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet()))); + assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); + } + + @Test + public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception { + final TelephonySubscriptionSnapshot snapshot = + new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet()); + + assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1)); + assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2)); + } + + @Test + public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception { + final TelephonySubscriptionSnapshot snapshot = + new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet()); + + assertEquals( + new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)), + snapshot.getAllSubIdsInGroup(TEST_PARCEL_UUID)); + } +} |