diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index a31c56a3b737..a17e79273e41 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -21,6 +21,7 @@ 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 android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; import android.annotation.NonNull; import android.annotation.Nullable; @@ -38,15 +39,19 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.CarrierPrivilegesListener; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -92,6 +97,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private final Map mReadySubIdsBySlotId = new HashMap<>(); @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener; + @NonNull + private final List mCarrierPrivilegesChangedListeners = + new ArrayList<>(); + @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot; public TelephonySubscriptionTracker( @@ -126,22 +135,71 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { }; } - /** Registers the receivers, and starts tracking subscriptions. */ + /** + * Registers the receivers, and starts tracking subscriptions. + * + *

Must always be run on the VcnManagementService thread. + */ public void register() { final HandlerExecutor executor = new HandlerExecutor(mHandler); + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); + filter.addAction(ACTION_MULTI_SIM_CONFIG_CHANGED); - mContext.registerReceiver( - this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED), null, mHandler); + mContext.registerReceiver(this, filter, null, mHandler); mSubscriptionManager.addOnSubscriptionsChangedListener( executor, mSubscriptionChangedListener); mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener); + + registerCarrierPrivilegesListeners(); } - /** Unregisters the receivers, and stops tracking subscriptions. */ + private void registerCarrierPrivilegesListeners() { + final HandlerExecutor executor = new HandlerExecutor(mHandler); + final int modemCount = mTelephonyManager.getActiveModemCount(); + try { + for (int i = 0; i < modemCount; i++) { + CarrierPrivilegesListener carrierPrivilegesListener = + new CarrierPrivilegesListener() { + @Override + public void onCarrierPrivilegesChanged( + @NonNull List privilegedPackageNames, + @NonNull int[] privilegedUids) { + // Re-trigger the synchronous check (which is also very cheap due + // to caching in CarrierPrivilegesTracker). This allows consistency + // with the onSubscriptionsChangedListener and broadcasts. + handleSubscriptionsChanged(); + } + }; + + mTelephonyManager.addCarrierPrivilegesListener( + i, executor, carrierPrivilegesListener); + mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener); + } + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Encounted exception registering carrier privileges listeners", e); + } + } + + /** + * Unregisters the receivers, and stops tracking subscriptions. + * + *

Must always be run on the VcnManagementService thread. + */ public void unregister() { mContext.unregisterReceiver(this); mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener); mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener); + + unregisterCarrierPrivilegesListeners(); + } + + private void unregisterCarrierPrivilegesListeners() { + for (CarrierPrivilegesListener carrierPrivilegesListener : + mCarrierPrivilegesChangedListeners) { + mTelephonyManager.removeCarrierPrivilegesListener(carrierPrivilegesListener); + } + mCarrierPrivilegesChangedListeners.clear(); } /** @@ -178,8 +236,6 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { // group. if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { - // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker - final TelephonyManager subIdSpecificTelephonyManager = mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); @@ -214,12 +270,39 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { */ @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; + switch (intent.getAction()) { + case ACTION_CARRIER_CONFIG_CHANGED: + handleActionCarrierConfigChanged(context, intent); + break; + case ACTION_MULTI_SIM_CONFIG_CHANGED: + handleActionMultiSimConfigChanged(context, intent); + break; + default: + Slog.v(TAG, "Unknown intent received with action: " + intent.getAction()); + } + } + + private void handleActionMultiSimConfigChanged(Context context, Intent intent) { + unregisterCarrierPrivilegesListeners(); + + // Clear invalid slotIds from the mReadySubIdsBySlotId map. + final int modemCount = mTelephonyManager.getActiveModemCount(); + final Iterator slotIdIterator = mReadySubIdsBySlotId.keySet().iterator(); + while (slotIdIterator.hasNext()) { + final int slotId = slotIdIterator.next(); + + if (slotId >= modemCount) { + slotIdIterator.remove(); + } } + registerCarrierPrivilegesListeners(); + handleSubscriptionsChanged(); + } + + private void handleActionCarrierConfigChanged(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 final int subId = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID); final int slotId = intent.getIntExtra(EXTRA_SLOT_INDEX, INVALID_SIM_SLOT_INDEX); diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 1f0df62fe72c..978bf3ed2e92 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -22,6 +22,7 @@ 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 android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; @@ -34,8 +35,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -57,6 +60,8 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.CarrierPrivilegesListener; +import android.util.ArrayMap; import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -83,7 +88,7 @@ public class TelephonySubscriptionTrackerTest { private static final String PACKAGE_NAME = TelephonySubscriptionTrackerTest.class.getPackage().getName(); 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_SIM_SLOT_INDEX = 0; 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; @@ -151,6 +156,8 @@ public class TelephonySubscriptionTrackerTest { @Before public void setUp() throws Exception { + doReturn(2).when(mTelephonyManager).getActiveModemCount(); + mCallback = mock(TelephonySubscriptionTrackerCallback.class); mTelephonySubscriptionTracker = new TelephonySubscriptionTracker(mContext, mHandler, mCallback, mDeps); @@ -180,6 +187,15 @@ public class TelephonySubscriptionTrackerTest { return captor.getValue(); } + private List getCarrierPrivilegesListeners() { + final ArgumentCaptor captor = + ArgumentCaptor.forClass(CarrierPrivilegesListener.class); + verify(mTelephonyManager, atLeastOnce()) + .addCarrierPrivilegesListener(anyInt(), any(), captor.capture()); + + return captor.getAllValues(); + } + private ActiveDataSubscriptionIdListener getActiveDataSubscriptionIdListener() { final ArgumentCaptor captor = ArgumentCaptor.forClass(TelephonyCallback.class); @@ -188,6 +204,11 @@ public class TelephonySubscriptionTrackerTest { return (ActiveDataSubscriptionIdListener) captor.getValue(); } + private Intent buildTestMultiSimConfigBroadcastIntent() { + Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED); + return intent; + } + private Intent buildTestBroadcastIntent(boolean hasValidSubscription) { Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED); intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX); @@ -239,12 +260,21 @@ public class TelephonySubscriptionTrackerTest { any(), eq(mHandler)); final IntentFilter filter = getIntentFilter(); - assertEquals(1, filter.countActions()); + assertEquals(2, filter.countActions()); assertTrue(filter.hasAction(ACTION_CARRIER_CONFIG_CHANGED)); + assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED)); verify(mSubscriptionManager) .addOnSubscriptionsChangedListener(any(HandlerExecutor.class), any()); assertNotNull(getOnSubscriptionsChangedListener()); + + verify(mTelephonyManager, times(2)) + .addCarrierPrivilegesListener(anyInt(), any(HandlerExecutor.class), any()); + verify(mTelephonyManager) + .addCarrierPrivilegesListener(eq(0), any(HandlerExecutor.class), any()); + verify(mTelephonyManager) + .addCarrierPrivilegesListener(eq(1), any(HandlerExecutor.class), any()); + assertEquals(2, getCarrierPrivilegesListeners().size()); } @Test @@ -255,6 +285,49 @@ public class TelephonySubscriptionTrackerTest { final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(eq(listener)); + + for (CarrierPrivilegesListener carrierPrivilegesListener : + getCarrierPrivilegesListeners()) { + verify(mTelephonyManager) + .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener)); + } + } + + @Test + public void testMultiSimConfigChanged() throws Exception { + final ArrayMap readySubIdsBySlotId = new ArrayMap<>(); + readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1); + readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX + 1, TEST_SUBSCRIPTION_ID_1); + + mTelephonySubscriptionTracker.setReadySubIdsBySlotId(readySubIdsBySlotId); + doReturn(1).when(mTelephonyManager).getActiveModemCount(); + + List carrierPrivilegesListeners = + getCarrierPrivilegesListeners(); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestMultiSimConfigBroadcastIntent()); + mTestLooper.dispatchAll(); + + for (CarrierPrivilegesListener carrierPrivilegesListener : carrierPrivilegesListeners) { + verify(mTelephonyManager) + .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener)); + } + + // Expect cache cleared for inactive slots. + assertNull( + mTelephonySubscriptionTracker + .getReadySubIdsBySlotId() + .get(TEST_SIM_SLOT_INDEX + 1)); + + // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other + // (2 previously registered during startup, for slots 0 & 1) + verify(mTelephonyManager, times(3)) + .addCarrierPrivilegesListener(anyInt(), any(HandlerExecutor.class), any()); + verify(mTelephonyManager, times(2)) + .addCarrierPrivilegesListener(eq(0), any(HandlerExecutor.class), any()); + + // Verify that this triggers a re-evaluation + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } @Test @@ -313,6 +386,17 @@ public class TelephonySubscriptionTrackerTest { verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } + @Test + public void testOnCarrierPrivilegesChanged() throws Exception { + setupReadySubIds(); + + final CarrierPrivilegesListener listener = getCarrierPrivilegesListeners().get(0); + listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {}); + mTestLooper.dispatchAll(); + + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); + } + @Test public void testReceiveBroadcast_ConfigReadyWithSubscriptions() throws Exception { mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));