Register OnSubscriptionsChangedListener and cache results.

Register a listener to get notified of SubscriptionInfo changes and
store all carrierIds of active subscriptions in a cache. The executor
for the listener callback runs on a different thread to the connectivity
thread but posts the SubscriptionInfo list to the connectivity thread
for caching.

Bug: 273451360
Test: atest FrameworksNetTests
Change-Id: I889d4da725ccda713367309c257622a0bf9939f3
This commit is contained in:
Hansen Kurli
2023-04-19 13:18:32 +00:00
parent ad03b887e6
commit c48d856976
5 changed files with 278 additions and 23 deletions

View File

@@ -311,7 +311,7 @@ public class AutomaticOnOffKeepaliveTracker {
mContext, mConnectivityServiceHandler);
mAlarmManager = mDependencies.getAlarmManager(context);
mKeepaliveStatsTracker = new KeepaliveStatsTracker(handler);
mKeepaliveStatsTracker = new KeepaliveStatsTracker(context, handler);
}
private void startTcpPollingAlarm(@NonNull AutomaticOnOffKeepalive ki) {

View File

@@ -16,14 +16,24 @@
package com.android.server.connectivity;
import static android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.annotation.NonNull;
import android.content.Context;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkSpecifier;
import android.net.TelephonyNetworkSpecifier;
import android.net.TransportInfo;
import android.net.wifi.WifiInfo;
import android.os.Handler;
import android.os.SystemClock;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.metrics.DailykeepaliveInfoReported;
@@ -31,6 +41,7 @@ import com.android.metrics.DurationForNumOfKeepalive;
import com.android.metrics.DurationPerNumOfKeepalive;
import com.android.metrics.KeepaliveLifetimeForCarrier;
import com.android.metrics.KeepaliveLifetimePerCarrier;
import com.android.modules.utils.BackgroundThread;
import java.util.ArrayList;
import java.util.HashMap;
@@ -52,6 +63,14 @@ public class KeepaliveStatsTracker {
@NonNull private final Handler mConnectivityServiceHandler;
@NonNull private final Dependencies mDependencies;
// Mapping of subId to carrierId. Updates are received from OnSubscriptionsChangedListener
private final SparseIntArray mCachedCarrierIdPerSubId = new SparseIntArray();
// The default subscription id obtained from SubscriptionManager.getDefaultSubscriptionId.
// Updates are done from the OnSubscriptionsChangedListener. Note that there is no callback done
// to OnSubscriptionsChangedListener when the default sub id changes.
// TODO: Register a listener for the default subId when it is possible.
private int mCachedDefaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
// Class to store network information, lifetime durations and active state of a keepalive.
private static final class KeepaliveStats {
// The carrier ID for a keepalive, or TelephonyManager.UNKNOWN_CARRIER_ID(-1) if not set.
@@ -214,16 +233,53 @@ public class KeepaliveStatsTracker {
}
}
public KeepaliveStatsTracker(@NonNull Handler handler) {
this(handler, new Dependencies());
public KeepaliveStatsTracker(@NonNull Context context, @NonNull Handler handler) {
this(context, handler, new Dependencies());
}
@VisibleForTesting
public KeepaliveStatsTracker(@NonNull Handler handler, @NonNull Dependencies dependencies) {
public KeepaliveStatsTracker(
@NonNull Context context,
@NonNull Handler handler,
@NonNull Dependencies dependencies) {
Objects.requireNonNull(context);
mDependencies = Objects.requireNonNull(dependencies);
mConnectivityServiceHandler = Objects.requireNonNull(handler);
final SubscriptionManager subscriptionManager =
Objects.requireNonNull(context.getSystemService(SubscriptionManager.class));
mLastUpdateDurationsTimestamp = mDependencies.getUptimeMillis();
// The default constructor for OnSubscriptionsChangedListener will always implicitly grab
// the looper of the current thread. In the case the current thread does not have a looper,
// this will throw. Therefore, post a runnable that creates it there.
// When the callback is called on the BackgroundThread, post a message on the CS handler
// thread to update the caches, which can only be touched there.
BackgroundThread.getHandler().post(() ->
subscriptionManager.addOnSubscriptionsChangedListener(
r -> r.run(), new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
final List<SubscriptionInfo> activeSubInfoList =
subscriptionManager.getActiveSubscriptionInfoList();
// A null subInfo list here indicates the current state is unknown
// but not necessarily empty, simply ignore it. Another call to the
// listener will be invoked in the future.
if (activeSubInfoList == null) return;
final int defaultSubId =
subscriptionManager.getDefaultSubscriptionId();
mConnectivityServiceHandler.post(() -> {
mCachedCarrierIdPerSubId.clear();
mCachedDefaultSubscriptionId = defaultSubId;
for (final SubscriptionInfo subInfo : activeSubInfoList) {
mCachedCarrierIdPerSubId.put(subInfo.getSubscriptionId(),
subInfo.getCarrierId());
}
});
}
}));
}
/** Ensures the list of duration metrics is large enough for number of registered keepalives. */
@@ -279,11 +335,33 @@ public class KeepaliveStatsTracker {
mLastUpdateDurationsTimestamp = timeNow;
}
// TODO(b/273451360): Make use of SubscriptionManager.OnSubscriptionsChangedListener since
// TelephonyManager.getSimCarrierId will be a cross-process call.
private int getCarrierId() {
// No implementation yet.
return TelephonyManager.UNKNOWN_CARRIER_ID;
// TODO: Move this function to frameworks/libs/net/.../NetworkCapabilitiesUtils.java
private static int getSubId(@NonNull NetworkCapabilities nc, int defaultSubId) {
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
final NetworkSpecifier networkSpecifier = nc.getNetworkSpecifier();
if (networkSpecifier instanceof TelephonyNetworkSpecifier) {
return ((TelephonyNetworkSpecifier) networkSpecifier).getSubscriptionId();
}
// Use the default subscriptionId.
return defaultSubId;
}
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
final TransportInfo info = nc.getTransportInfo();
if (info instanceof WifiInfo) {
return ((WifiInfo) info).getSubscriptionId();
}
}
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
private int getCarrierId(@NonNull NetworkCapabilities networkCapabilities) {
// Try to get the correct subscription id.
final int subId = getSubId(networkCapabilities, mCachedDefaultSubscriptionId);
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return TelephonyManager.UNKNOWN_CARRIER_ID;
}
return mCachedCarrierIdPerSubId.get(subId, TelephonyManager.UNKNOWN_CARRIER_ID);
}
private int getTransportTypes(@NonNull NetworkCapabilities networkCapabilities) {
@@ -313,7 +391,7 @@ public class KeepaliveStatsTracker {
final KeepaliveStats newKeepaliveStats =
new KeepaliveStats(
getCarrierId(), getTransportTypes(nc), intervalSeconds, timeNow);
getCarrierId(nc), getTransportTypes(nc), intervalSeconds, timeNow);
mKeepaliveStatsPerId.put(keepaliveId, newKeepaliveStats);
}

View File

@@ -355,6 +355,7 @@ import android.os.UserManager;
import android.provider.Settings;
import android.security.Credentials;
import android.system.Os;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.NrQosSessionAttributes;
@@ -616,6 +617,7 @@ public class ConnectivityServiceTest {
@Mock BroadcastOptionsShim mBroadcastOptionsShim;
@Mock ActivityManager mActivityManager;
@Mock DestroySocketsWrapper mDestroySocketsWrapper;
@Mock SubscriptionManager mSubscriptionManager;
// BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
// underlying binder calls.
@@ -740,6 +742,7 @@ public class ConnectivityServiceTest {
if (Context.PAC_PROXY_SERVICE.equals(name)) return mPacProxyManager;
if (Context.TETHERING_SERVICE.equals(name)) return mTetheringManager;
if (Context.ACTIVITY_SERVICE.equals(name)) return mActivityManager;
if (Context.TELEPHONY_SUBSCRIPTION_SERVICE.equals(name)) return mSubscriptionManager;
return super.getSystemService(name);
}

View File

@@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.ignoreStubs;
@@ -67,6 +68,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -121,6 +123,7 @@ public class AutomaticOnOffKeepaliveTrackerTest {
@Mock Context mCtx;
@Mock AlarmManager mAlarmManager;
@Mock NetworkAgentInfo mNai;
@Mock SubscriptionManager mSubscriptionManager;
TestKeepaliveTracker mKeepaliveTracker;
AOOTestHandler mTestHandler;
@@ -298,10 +301,22 @@ public class AutomaticOnOffKeepaliveTrackerTest {
}
}
private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
doReturn(serviceName).when(mCtx).getSystemServiceName(serviceClass);
doReturn(service).when(mCtx).getSystemService(serviceName);
if (mCtx.getSystemService(serviceClass) == null) {
// Test is using mockito-extended
doCallRealMethod().when(mCtx).getSystemService(serviceClass);
}
}
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mockService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
mSubscriptionManager);
mNai.networkCapabilities =
new NetworkCapabilities.Builder().addTransportType(TRANSPORT_CELLULAR).build();
mNai.networkInfo = new NetworkInfo(TYPE_MOBILE, 0 /* subtype */, "LTE", "LTE");

View File

@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
@@ -25,13 +26,24 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
@@ -41,39 +53,68 @@ import com.android.metrics.DurationForNumOfKeepalive;
import com.android.metrics.DurationPerNumOfKeepalive;
import com.android.metrics.KeepaliveLifetimeForCarrier;
import com.android.metrics.KeepaliveLifetimePerCarrier;
import com.android.modules.utils.BackgroundThread;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public class KeepaliveStatsTrackerTest {
private static final int TIMEOUT_MS = 30_000;
private static final int TEST_SLOT = 1;
private static final int TEST_SLOT2 = 2;
private static final int TEST_KEEPALIVE_INTERVAL_SEC = 10;
private static final int TEST_KEEPALIVE_INTERVAL2_SEC = 20;
// Carrier id not yet implemented, assume it returns unknown for now.
private static final int TEST_CARRIER_ID = TelephonyManager.UNKNOWN_CARRIER_ID;
private static final int TEST_SUB_ID_1 = 1;
private static final int TEST_SUB_ID_2 = 2;
private static final int TEST_CARRIER_ID_1 = 135;
private static final int TEST_CARRIER_ID_2 = 246;
private static final Network TEST_NETWORK = new Network(123);
private static final NetworkCapabilities TEST_NETWORK_CAPABILITIES =
new NetworkCapabilities.Builder().addTransportType(TRANSPORT_CELLULAR).build();
buildCellNetworkCapabilitiesWithSubId(TEST_SUB_ID_1);
private static final NetworkCapabilities TEST_NETWORK_CAPABILITIES_2 =
buildCellNetworkCapabilitiesWithSubId(TEST_SUB_ID_2);
private static NetworkCapabilities buildCellNetworkCapabilitiesWithSubId(int subId) {
final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
new TelephonyNetworkSpecifier.Builder().setSubscriptionId(subId).build();
return new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.setNetworkSpecifier(telephonyNetworkSpecifier)
.build();
}
private HandlerThread mHandlerThread;
private Handler mTestHandler;
private KeepaliveStatsTracker mKeepaliveStatsTracker;
@Mock private Context mContext;
@Mock private KeepaliveStatsTracker.Dependencies mDependencies;
@Mock private SubscriptionManager mSubscriptionManager;
private OnSubscriptionsChangedListener getOnSubscriptionsChangedListener() {
final ArgumentCaptor<OnSubscriptionsChangedListener> listenerCaptor =
ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class);
verify(mSubscriptionManager)
.addOnSubscriptionsChangedListener(any(), listenerCaptor.capture());
return listenerCaptor.getValue();
}
private static final class KeepaliveCarrierStats {
public final int carrierId;
@@ -116,23 +157,53 @@ public class KeepaliveStatsTrackerTest {
// Use the default test carrier id, transportType and keepalive interval.
private KeepaliveCarrierStats getDefaultCarrierStats(int lifetimeMs, int activeLifetimeMs) {
return new KeepaliveCarrierStats(
TEST_CARRIER_ID,
TEST_CARRIER_ID_1,
/* transportTypes= */ (1 << TRANSPORT_CELLULAR),
TEST_KEEPALIVE_INTERVAL_SEC * 1000,
lifetimeMs,
activeLifetimeMs);
}
private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
doReturn(serviceName).when(mContext).getSystemServiceName(serviceClass);
doReturn(service).when(mContext).getSystemService(serviceName);
if (mContext.getSystemService(serviceClass) == null) {
// Test is using mockito-extended
doCallRealMethod().when(mContext).getSystemService(serviceClass);
}
}
private SubscriptionInfo makeSubInfoMock(int subId, int carrierId) {
final SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
doReturn(subId).when(subInfo).getSubscriptionId();
doReturn(carrierId).when(subInfo).getCarrierId();
return subInfo;
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
mSubscriptionManager);
final SubscriptionInfo subInfo1 = makeSubInfoMock(TEST_SUB_ID_1, TEST_CARRIER_ID_1);
final SubscriptionInfo subInfo2 = makeSubInfoMock(TEST_SUB_ID_2, TEST_CARRIER_ID_2);
doReturn(List.of(subInfo1, subInfo2))
.when(mSubscriptionManager)
.getActiveSubscriptionInfoList();
mHandlerThread = new HandlerThread("KeepaliveStatsTrackerTest");
mHandlerThread.start();
mTestHandler = new Handler(mHandlerThread.getLooper());
setUptimeMillis(0);
mKeepaliveStatsTracker = new KeepaliveStatsTracker(mTestHandler, mDependencies);
mKeepaliveStatsTracker = new KeepaliveStatsTracker(mContext, mTestHandler, mDependencies);
HandlerUtils.waitForIdle(BackgroundThread.getHandler(), TIMEOUT_MS);
// Initial onSubscriptionsChanged.
getOnSubscriptionsChangedListener().onSubscriptionsChanged();
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
}
private void setUptimeMillis(long time) {
@@ -158,13 +229,18 @@ public class KeepaliveStatsTrackerTest {
}
private void onStartKeepalive(long time, int slot, int intervalSeconds) {
onStartKeepalive(time, slot, TEST_NETWORK_CAPABILITIES, intervalSeconds);
}
private void onStartKeepalive(long time, int slot, NetworkCapabilities nc) {
onStartKeepalive(time, slot, nc, TEST_KEEPALIVE_INTERVAL_SEC);
}
private void onStartKeepalive(
long time, int slot, NetworkCapabilities nc, int intervalSeconds) {
setUptimeMillis(time);
visibleOnHandlerThread(mTestHandler, () ->
mKeepaliveStatsTracker.onStartKeepalive(
TEST_NETWORK,
slot,
TEST_NETWORK_CAPABILITIES,
intervalSeconds));
mKeepaliveStatsTracker.onStartKeepalive(TEST_NETWORK, slot, nc, intervalSeconds));
}
private void onPauseKeepalive(long time, int slot) {
@@ -732,7 +808,8 @@ public class KeepaliveStatsTrackerTest {
onStartKeepalive(startTime1, TEST_SLOT);
onStartKeepalive(startTime2, TEST_SLOT2, TEST_KEEPALIVE_INTERVAL2_SEC);
onStartKeepalive(startTime2, TEST_SLOT2, TEST_NETWORK_CAPABILITIES_2,
TEST_KEEPALIVE_INTERVAL2_SEC);
onStopKeepalive(stopTime1, TEST_SLOT);
@@ -759,7 +836,7 @@ public class KeepaliveStatsTrackerTest {
getDefaultCarrierStats(stopTime1 - startTime1, stopTime1 - startTime1);
final KeepaliveCarrierStats expectKeepaliveCarrierStats2 =
new KeepaliveCarrierStats(
TEST_CARRIER_ID,
TEST_CARRIER_ID_2,
/* transportTypes= */ (1 << TRANSPORT_CELLULAR),
TEST_KEEPALIVE_INTERVAL2_SEC * 1000,
writeTime - startTime2,
@@ -783,7 +860,7 @@ public class KeepaliveStatsTrackerTest {
// Only the keepalive with interval of intervalSec2 is present.
final KeepaliveCarrierStats expectKeepaliveCarrierStats3 =
new KeepaliveCarrierStats(
TEST_CARRIER_ID,
TEST_CARRIER_ID_2,
/* transportTypes= */ (1 << TRANSPORT_CELLULAR),
TEST_KEEPALIVE_INTERVAL2_SEC * 1000,
writeTime2 - writeTime,
@@ -861,4 +938,86 @@ public class KeepaliveStatsTrackerTest {
getDefaultCarrierStats(expectRegisteredDurations[1], expectActiveDurations[1])
});
}
@Test
public void testCarrierIdChange_changeBeforeStart() {
// Update the list to only have sub_id_2 with carrier_id_1.
final SubscriptionInfo subInfo = makeSubInfoMock(TEST_SUB_ID_2, TEST_CARRIER_ID_1);
doReturn(List.of(subInfo)).when(mSubscriptionManager).getActiveSubscriptionInfoList();
getOnSubscriptionsChangedListener().onSubscriptionsChanged();
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
final int startTime = 1000;
final int writeTime = 5000;
onStartKeepalive(startTime, TEST_SLOT, TEST_NETWORK_CAPABILITIES);
onStartKeepalive(startTime, TEST_SLOT2, TEST_NETWORK_CAPABILITIES_2);
final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
buildKeepaliveMetrics(writeTime);
// The network with sub_id_1 has an unknown carrier id.
final KeepaliveCarrierStats expectKeepaliveCarrierStats1 =
new KeepaliveCarrierStats(
TelephonyManager.UNKNOWN_CARRIER_ID,
/* transportTypes= */ (1 << TRANSPORT_CELLULAR),
TEST_KEEPALIVE_INTERVAL_SEC * 1000,
writeTime - startTime,
writeTime - startTime);
// The network with sub_id_2 has carrier_id_1.
final KeepaliveCarrierStats expectKeepaliveCarrierStats2 =
new KeepaliveCarrierStats(
TEST_CARRIER_ID_1,
/* transportTypes= */ (1 << TRANSPORT_CELLULAR),
TEST_KEEPALIVE_INTERVAL_SEC * 1000,
writeTime - startTime,
writeTime - startTime);
assertDailyKeepaliveInfoReported(
dailyKeepaliveInfoReported,
/* expectRegisteredDurations= */ new int[] {startTime, 0, writeTime - startTime},
/* expectActiveDurations= */ new int[] {startTime, 0, writeTime - startTime},
new KeepaliveCarrierStats[] {
expectKeepaliveCarrierStats1, expectKeepaliveCarrierStats2
});
}
@Test
public void testCarrierIdFromWifiInfo() {
final int startTime = 1000;
final int writeTime = 5000;
final WifiInfo wifiInfo = mock(WifiInfo.class);
final WifiInfo wifiInfoCopy = mock(WifiInfo.class);
// Building NetworkCapabilities stores a copy of the WifiInfo with makeCopy.
doReturn(wifiInfoCopy).when(wifiInfo).makeCopy(anyLong());
doReturn(TEST_SUB_ID_1).when(wifiInfo).getSubscriptionId();
doReturn(TEST_SUB_ID_1).when(wifiInfoCopy).getSubscriptionId();
final NetworkCapabilities nc =
new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_WIFI)
.setTransportInfo(wifiInfo)
.build();
onStartKeepalive(startTime, TEST_SLOT, nc);
final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
buildKeepaliveMetrics(writeTime);
final KeepaliveCarrierStats expectKeepaliveCarrierStats =
new KeepaliveCarrierStats(
TEST_CARRIER_ID_1,
/* transportTypes= */ (1 << TRANSPORT_WIFI),
TEST_KEEPALIVE_INTERVAL_SEC * 1000,
writeTime - startTime,
writeTime - startTime);
assertDailyKeepaliveInfoReported(
dailyKeepaliveInfoReported,
/* expectRegisteredDurations= */ new int[] {startTime, writeTime - startTime},
/* expectActiveDurations= */ new int[] {startTime, writeTime - startTime},
new KeepaliveCarrierStats[] {expectKeepaliveCarrierStats});
}
}