Merge changes Id80bfca1,I8254c3f4 into main
* changes:
clatd: one less shutdown log line
clatd: rework sigterm & error handling
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
index 1f5fcfa..edc5515 100644
--- a/Tethering/apex/canned_fs_config
+++ b/Tethering/apex/canned_fs_config
@@ -1,3 +1,3 @@
-/bin/for-system 0 1000 0750
+/bin/for-system 1029 1000 0750
/bin/for-system/clatd 1029 1029 06755
/bin/netbpfload 0 0 0750
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 2f9c3bc..a8a471d 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -340,6 +340,11 @@
* @hide
*/
public static final int TETHER_ERROR_BLUETOOTH_SERVICE_PENDING = 19;
+ /**
+ * Never used outside Tethering.java.
+ * @hide
+ */
+ public static final int TETHER_ERROR_SOFT_AP_CALLBACK_PENDING = 20;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
index 9c61716..c91ff58 100644
--- a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
+++ b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
@@ -240,6 +240,11 @@
mServingRequests.entrySet().removeIf(e -> e.getValue().getTetheringType() == type);
}
+ @VisibleForTesting
+ List<TetheringRequest> getServingTetheringRequests() {
+ return new ArrayList<>(mServingRequests.values());
+ }
+
/**
* Returns an existing (pending or serving) request that fuzzy matches the given request.
* Optionally specify matchUid to only return requests with the same uid.
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index c7ae353..0cf008b 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -47,6 +47,7 @@
import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_SOFT_AP_CALLBACK_PENDING;
import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST;
@@ -114,6 +115,7 @@
import android.net.TetheringManager.TetheringRequest;
import android.net.Uri;
import android.net.ip.IpServer;
+import android.net.wifi.SoftApState;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pGroup;
@@ -775,7 +777,7 @@
final int result;
switch (type) {
case TETHERING_WIFI:
- result = setWifiTethering(enable);
+ result = setWifiTethering(enable, request, listener);
break;
case TETHERING_USB:
result = setUsbTethering(enable);
@@ -800,6 +802,9 @@
// The result of Bluetooth tethering will be sent after the pan service connects.
if (result == TETHER_ERROR_BLUETOOTH_SERVICE_PENDING) return;
+ // The result of Wifi tethering will be sent after the SoftApCallback result.
+ if (result == TETHER_ERROR_SOFT_AP_CALLBACK_PENDING) return;
+
sendTetherResultAndRemoveOnError(request, listener, result);
}
@@ -824,7 +829,8 @@
}
}
- private int setWifiTethering(final boolean enable) {
+ private int setWifiTethering(final boolean enable, TetheringRequest request,
+ IIntResultListener listener) {
final long ident = Binder.clearCallingIdentity();
try {
final WifiManager mgr = getWifiManager();
@@ -832,8 +838,34 @@
mLog.e("setWifiTethering: failed to get WifiManager!");
return TETHER_ERROR_SERVICE_UNAVAIL;
}
- if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
- || (!enable && mgr.stopSoftAp())) {
+ final boolean success;
+ if (enable) {
+ if (isTetheringWithSoftApConfigEnabled()) {
+ // Notes:
+ // - A call to startTetheredHotspot can only succeed if the SoftAp is idle. If
+ // the SoftAp is running or is being disabled, the call will fail.
+ // - If a call to startTetheredHotspot fails, the callback is immediately called
+ // with WIFI_AP_STATE_FAILED and a null interface.
+ // - If a call to startTetheredHotspot succeeds, the passed-in callback is the
+ // only callback that will receive future WIFI_AP_STATE_ENABLED and
+ // WIFI_AP_STATE_DISABLED events in the future, until another call to
+ // startTetheredHotspot succeeds, at which point the old callback will stop
+ // receiving any events.
+ // - Wifi may decide to restart the hotspot at any time (such as for a CC
+ // change), and if it does so, it will send WIFI_AP_STATE_DISABLED and then
+ // either WIFI_AP_STATE_ENABLED or (if restarting fails) WIFI_AP_STATE_FAILED.
+ mgr.startTetheredHotspot(request, mExecutor,
+ new StartTetheringSoftApCallback(listener));
+ // Result isn't used since we get the real result via
+ // StartTetheringSoftApCallback.
+ return TETHER_ERROR_SOFT_AP_CALLBACK_PENDING;
+ }
+ success = mgr.startTetheredHotspot(null);
+ } else {
+ success = mgr.stopSoftAp();
+ }
+
+ if (success) {
return TETHER_ERROR_NO_ERROR;
}
} finally {
@@ -1458,6 +1490,9 @@
final String ifname = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
final int ipmode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, IFACE_IP_MODE_UNSPECIFIED);
+ // In B+, Tethered AP is handled by StartTetheringSoftApCallback.
+ if (isTetheringWithSoftApConfigEnabled() && ipmode == IFACE_IP_MODE_TETHERED) return;
+
switch (curState) {
case WifiManager.WIFI_AP_STATE_ENABLING:
// We can see this state on the way to both enabled and failure states.
@@ -1538,12 +1573,64 @@
}
}
+ class StartTetheringSoftApCallback implements SoftApCallback {
+
+ @Nullable
+ IIntResultListener mPendingListener;
+
+ StartTetheringSoftApCallback(IIntResultListener pendingListener) {
+ mPendingListener = pendingListener;
+ }
+
+ @Override
+ public void onStateChanged(SoftApState softApState) {
+ final int state = softApState.getState();
+ final String iface = softApState.getIface();
+ final TetheringRequest request = softApState.getTetheringRequest();
+ switch (softApState.getState()) {
+ case WifiManager.WIFI_AP_STATE_ENABLED:
+ enableIpServing(request, iface);
+ // If stopTethering has already been called, IP serving will still be started,
+ // but as soon as the wifi code processes the stop, WIFI_AP_STATE_DISABLED will
+ // be sent and tethering will be stopped again.
+ sendTetherResultAndRemoveOnError(request, mPendingListener,
+ TETHER_ERROR_NO_ERROR);
+ mPendingListener = null;
+ break;
+ case WifiManager.WIFI_AP_STATE_FAILED:
+ // TODO: if a call to startTethering happens just after a call to stopTethering,
+ // the start will fail because hotspot is still being disabled. This likely
+ // cannot be fixed in tethering code but must be fixed in WiFi.
+ sendTetherResultAndRemoveOnError(request, mPendingListener,
+ TETHER_ERROR_INTERNAL_ERROR);
+ mPendingListener = null;
+ break;
+ case WifiManager.WIFI_AP_STATE_DISABLED:
+ // TODO(b/403164072): SoftAP may restart due to CC change, in which we'll get
+ // DISABLED -> ENABLED (or FAILED). Before the transition back to ENABLED is
+ // complete, it is possible that a new Wifi request is accepted since there's no
+ // active request to fuzzy-match it, which will unexpectedly cause Wifi to
+ // overwrite this SoftApCallback. This should be fixed in Wifi to disallow any
+ // new calls to startTetheredHotspot while SoftAP is restarting.
+ disableWifiIpServing(iface, state);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
@VisibleForTesting
List<TetheringRequest> getPendingTetheringRequests() {
return mRequestTracker.getPendingTetheringRequests();
}
@VisibleForTesting
+ List<TetheringRequest> getServingTetheringRequests() {
+ return mRequestTracker.getServingTetheringRequests();
+ }
+
+ @VisibleForTesting
boolean isTetheringActive() {
return getTetheredIfaces().length > 0;
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 1083ef9..dc3cbd2 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -69,6 +69,7 @@
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -169,6 +170,7 @@
import android.net.ip.IpServer;
import android.net.ip.RouterAdvertisementDaemon;
import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.SoftApState;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.SoftApCallback;
@@ -241,6 +243,7 @@
import java.util.List;
import java.util.Set;
import java.util.Vector;
+import java.util.concurrent.Executor;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -842,6 +845,38 @@
mLooper.dispatchAll();
}
+ private void sendStartTetheringSoftApCallback(int state, TetheringRequest request,
+ String ifname) {
+ ArgumentCaptor<SoftApCallback> callbackCaptor =
+ ArgumentCaptor.forClass(SoftApCallback.class);
+ verify(mWifiManager, atLeastOnce()).startTetheredHotspot(any(TetheringRequest.class),
+ any(Executor.class), callbackCaptor.capture());
+ SoftApState softApState = mock(SoftApState.class);
+ when(softApState.getState()).thenReturn(state);
+ when(softApState.getTetheringRequest()).thenReturn(request);
+ when(softApState.getIface()).thenReturn(ifname);
+ callbackCaptor.getValue().onStateChanged(softApState);
+ mLooper.dispatchAll();
+ }
+
+ private void verifyWifiTetheringRequested() {
+ if (mTetheringDependencies.isTetheringWithSoftApConfigEnabled()) {
+ verify(mWifiManager).startTetheredHotspot(any(), any(), any());
+ } else {
+ verify(mWifiManager).startTetheredHotspot(null);
+ }
+ verify(mWifiManager, never()).stopSoftAp();
+ verifyNoMoreInteractions(mWifiManager);
+ }
+
+ private void sendSoftApEvent(int state, TetheringRequest request, String ifname) {
+ if (mTetheringDependencies.isTetheringWithSoftApConfigEnabled()) {
+ sendStartTetheringSoftApCallback(state, request, ifname);
+ } else {
+ sendWifiApStateChanged(state, ifname, IFACE_IP_MODE_TETHERED);
+ }
+ }
+
private static final String[] P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST = {
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_WIFI_STATE
@@ -1964,11 +1999,9 @@
initTetheringOnTestThread();
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
- null);
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, null);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startTetheredHotspot(null);
- verifyNoMoreInteractions(mWifiManager);
+ verifyWifiTetheringRequested();
verifyNoMoreInteractions(mNetd);
// Emulate externally-visible WifiManager effects, causing the
@@ -1997,14 +2030,15 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
+ // B+ uses SoftApCallback instead of WIFI_AP_STATE_CHANGED for tethered hotspot.
+ mTetheringWithSoftApConfigEnabled = false;
initTetheringOnTestThread();
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startTetheredHotspot(null);
- verifyNoMoreInteractions(mWifiManager);
+ verifyWifiTetheringRequested();
verifyNoMoreInteractions(mNetd);
// Emulate externally-visible WifiManager effects, causing the
@@ -2042,6 +2076,122 @@
verifyStopHotpot();
}
+ @Test
+ public void startWifiTetheringWithSoftApConfigurationSuccess() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Emulate pressing the WiFi tethering button.
+ TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(new SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+ .build())
+ .build();
+ IIntResultListener startResultListener = mock(IIntResultListener.class);
+ mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+ mLooper.dispatchAll();
+ verifyNoMoreInteractions(mNetd);
+ verify(startResultListener, never()).onResult(anyInt());
+ // Emulate Wifi iface enabled
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
+
+ verifyStartHotspot();
+ verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
+ verify(startResultListener).onResult(TETHER_ERROR_NO_ERROR);
+ }
+
+ @Test
+ public void startWifiTetheringWithSoftApConfigurationFailure() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Emulate pressing the WiFi tethering button.
+ TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(new SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+ .build())
+ .build();
+ IIntResultListener startResultListener = mock(IIntResultListener.class);
+ mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+ mLooper.dispatchAll();
+ verify(startResultListener, never()).onResult(anyInt());
+ // Emulate Wifi iface failure
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_FAILED, request, TEST_WLAN_IFNAME);
+
+ verify(startResultListener).onResult(TETHER_ERROR_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void startWifiTetheringWithSoftApConfigurationRestartAfterStarting() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+ .build();
+ final TetheringInterface wifiIface = new TetheringInterface(
+ TETHERING_WIFI, TEST_WLAN_IFNAME);
+ final TetheringInterface wifiIfaceWithConfig = new TetheringInterface(
+ TETHERING_WIFI, TEST_WLAN_IFNAME, softApConfig);
+
+ // 1. Register one callback before running any tethering.
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ assertTetherStatesNotNullButEmpty(callback.pollTetherStatesChanged());
+ // Emulate pressing the WiFi tethering button.
+ TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig)
+ .build();
+ IIntResultListener startResultListener = mock(IIntResultListener.class);
+ mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+ mLooper.dispatchAll();
+
+ // Wifi success
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
+ verifyStartHotspot();
+ TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
+ tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIfaceWithConfig});
+ verify(startResultListener).onResult(TETHER_ERROR_NO_ERROR);
+
+ // Restart Wifi
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, request, TEST_WLAN_IFNAME);
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
+
+ // Verify we go from TETHERED -> AVAILABLE -> TETHERED with the same config.
+ tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
+ tetherState = callback.pollTetherStatesChanged();
+ assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIfaceWithConfig});
+ }
+
+ @Test
+ public void startWifiApBroadcastDoesNotStartIpServing() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Call startTethering for wifi
+ TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(new SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes("SSID".getBytes(StandardCharsets.UTF_8)))
+ .build())
+ .build();
+ IIntResultListener startResultListener = mock(IIntResultListener.class);
+ mTethering.startTethering(request, TEST_CALLER_PKG, startResultListener);
+ mLooper.dispatchAll();
+
+ // WIFI_AP_STATE_CHANGED broadcast should be ignored since we should only be using
+ // SoftApCallback for tethered AP.
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ verify(mNetd, never()).tetherStartWithConfiguration(any());
+ verify(mNotificationUpdater, never()).onDownstreamChanged(DOWNSTREAM_NONE);
+ verify(mWifiManager, never()).updateInterfaceIpState(
+ TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+ assertTrue(mTethering.getServingTetheringRequests().isEmpty());
+ }
+
// TODO: Test with and without interfaceStatusChanged().
@Test
public void failureEnablingIpForwarding() throws Exception {
@@ -2049,11 +2199,10 @@
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
- null);
+ TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startTetheredHotspot(null);
- verifyNoMoreInteractions(mWifiManager);
+ verifyWifiTetheringRequested();
verifyNoMoreInteractions(mNetd);
verify(mTetheringMetrics).createBuilder(eq(TETHERING_WIFI), anyString());
@@ -2061,7 +2210,7 @@
// per-interface state machine to start up, and telling us that
// tethering mode is to be started.
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
// We verify get/set called three times here: twice for setup and once during
// teardown because all events happen over the course of the single
@@ -2390,7 +2539,8 @@
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
initTetheringUpstream(upstreamState);
- mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
+ TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+ mTethering.startTethering(request, TEST_CALLER_PKG,
null);
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
mLooper.dispatchAll();
@@ -2398,7 +2548,7 @@
// Starting in B, ignore the interfaceStatusChanged
callback.assertNoStateChangeCallback();
}
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
mLooper.dispatchAll();
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
@@ -2426,8 +2576,7 @@
if (isAtLeastT()) {
// After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
callback2.assertNoStateChangeCallback();
- sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
- IFACE_IP_MODE_TETHERED);
+ sendSoftApEvent(WIFI_AP_STATE_DISABLED, request, TEST_WLAN_IFNAME);
}
tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
@@ -2500,7 +2649,8 @@
mLooper.dispatchAll();
// Netd "up" event should not trigger a state change callback in B+.
callback.assertNoStateChangeCallback();
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest,
+ TEST_WLAN_IFNAME);
// Verify we see Available -> Tethered states
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
@@ -2520,8 +2670,8 @@
// Disable wifi tethering
mTethering.stopTethering(TETHERING_WIFI);
mLooper.dispatchAll();
- sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
- IFACE_IP_MODE_TETHERED);
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, tetheringRequest,
+ TEST_WLAN_IFNAME);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
@@ -2566,7 +2716,7 @@
ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
mLooper.dispatchAll();
- successListener.assertHasResult();
+ successListener.assertDoesNotHaveResult();
// Try starting wifi tethering with various fuzzy-matching requests and verify we get
// TETHER_ERROR_DUPLICATE_REQUEST.
@@ -2581,8 +2731,7 @@
ResultListener differentIpAddrListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
mTethering.startTethering(differentIpAddr, TEST_CALLER_PKG, differentIpAddrListener);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startTetheredHotspot(any());
- verify(mWifiManager, never()).stopSoftAp();
+ verifyWifiTetheringRequested();
differentIpAddrListener.assertHasResult();
// Different UID
@@ -2593,13 +2742,13 @@
mTethering.startTethering(differentUid, TEST_CALLER_PKG, differentUidListener);
mLooper.dispatchAll();
differentUidListener.assertHasResult();
- verify(mWifiManager, times(1)).startTetheredHotspot(any());
- verify(mWifiManager, never()).stopSoftAp();
+ verifyWifiTetheringRequested();
- // Mock the link layer event to start IP serving and verify we still get
- // TETHER_ERROR_DUPLICATE_REQUEST even though the request is no longer pending and is
- // already serving.
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ // Mock the link layer event to start IP serving and verify
+ // 1) The original request's result listener is called.
+ // 2) We still get TETHER_ERROR_DUPLICATE_REQUEST for new requests.
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
+ successListener.assertHasResult();
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
@@ -2613,7 +2762,7 @@
mLooper.dispatchAll();
differentIpAddrListener.assertHasResult();
differentUidListener.assertHasResult();
- verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verify(mWifiManager, times(1)).startTetheredHotspot(any(), any(), any());
verify(mWifiManager, never()).stopSoftAp();
}
@@ -2630,8 +2779,8 @@
ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
successListener.assertHasResult();
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
// Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
@@ -2662,9 +2811,8 @@
ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
successListener.assertHasResult();
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
-
// Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
@@ -2672,12 +2820,15 @@
failureListener.assertHasResult();
// Trigger wifi ap state change to tell IpServer it's unwanted.
- sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_DISABLED, tetheringRequest,
+ TEST_WLAN_IFNAME);
// We should be able to request the same Wifi again
ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest,
+ TEST_WLAN_IFNAME);
successListener2.assertHasResult();
}
@@ -2697,14 +2848,15 @@
ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
successListener.assertHasResult();
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
// We should be able to request the same Wifi again since the DHCP server transitioned the
// IpServer back to InitialState.
ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
successListener2.assertHasResult();
}
@@ -2721,7 +2873,10 @@
ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
mLooper.dispatchAll();
- successListener.assertHasResult();
+ ArgumentCaptor<SoftApCallback> callbackCaptor =
+ ArgumentCaptor.forClass(SoftApCallback.class);
+ verify(mWifiManager, atLeastOnce()).startTetheredHotspot(any(TetheringRequest.class),
+ any(Executor.class), callbackCaptor.capture());
// Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
@@ -2736,6 +2891,20 @@
ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
+ successListener2.assertHasResult();
+
+ // Mock the first request going up and then down from the stop request.
+ SoftApState softApState = mock(SoftApState.class);
+ when(softApState.getState()).thenReturn(WIFI_AP_STATE_ENABLED);
+ when(softApState.getTetheringRequest()).thenReturn(tetheringRequest);
+ when(softApState.getIface()).thenReturn(TEST_WLAN_IFNAME);
+ callbackCaptor.getValue().onStateChanged(softApState);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+
+ // Mock the second request going up
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
successListener2.assertHasResult();
}
@@ -2752,8 +2921,8 @@
ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
successListener.assertHasResult();
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
// Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
@@ -2768,6 +2937,7 @@
ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
mLooper.dispatchAll();
+ sendStartTetheringSoftApCallback(WIFI_AP_STATE_ENABLED, tetheringRequest, TEST_WLAN_IFNAME);
successListener2.assertHasResult();
}
@@ -3216,7 +3386,7 @@
serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null),
TEST_CALLER_PKG, null);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verifyWifiTetheringRequested();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
// Call legacyTether on the interface before the link layer event comes back.
@@ -3588,8 +3758,11 @@
reset(mDhcpServer);
// Run wifi tethering.
+ TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
any(), dhcpEventCbsCaptor.capture());
eventCallbacks = dhcpEventCbsCaptor.getValue();
@@ -3652,8 +3825,12 @@
});
callback.expectTetheredClientChanged(Collections.emptyList());
+ TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
+ verifyWifiTetheringRequested();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
@@ -4184,8 +4361,11 @@
@Test
public void testIpv4AddressForSapAndLohsConcurrency() throws Exception {
initTetheringOnTestThread();
+ TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
@@ -4234,14 +4414,13 @@
assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
- null);
+ TetheringRequest request = createTetheringRequest(TETHERING_WIFI);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
- verify(mWifiManager).startTetheredHotspot(null);
- verifyNoMoreInteractions(mWifiManager);
+ verifyWifiTetheringRequested();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ sendSoftApEvent(WIFI_AP_STATE_ENABLED, request, TEST_WLAN_IFNAME);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mWifiManager).updateInterfaceIpState(
diff --git a/bpf/progs/netd.c b/bpf/progs/netd.c
index 08635b3..b146e45 100644
--- a/bpf/progs/netd.c
+++ b/bpf/progs/netd.c
@@ -597,7 +597,7 @@
// V | | | x | x | x | x | x | x | | (netbpfload)
// U | | x | x | x | x | x | x | | |
// T | x | x | x | x | x | x | | | | (magic netbpfload)
-// S | x | x | x | x | x | | | | | (platform loads offload)
+// S | x | x | x | x | x | | | | | (dns netbpfload for offload)
// R | x | x | x | x | | | | | | (no mainline ebpf)
//
// Not relevant for eBPF, but R can also run on 4.4
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index c0082bb..622fba8 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -104,7 +104,7 @@
// First verify the clatd directory and binary,
// since this is built into the apex file system image,
// failures here are 99% likely to be build problems.
- V(kClatdDir, S_IFDIR|0750, ROOT, SYSTEM, "system_file", DIR);
+ V(kClatdDir, S_IFDIR|0750, CLAT, SYSTEM, "system_file", DIR);
V(kClatdBin, S_IFREG|S_ISUID|S_ISGID|0755, CLAT, CLAT, "clatd_exec", BIN);
// Move on to verifying that the bpf programs and maps are as expected.
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index 348df3b..8059e22 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -26,7 +26,7 @@
get_apf_counter,
get_apf_counters_from_dumpsys,
get_ipv4_addresses,
- get_ipv6_addresses,
+ get_non_tentative_ipv6_addresses,
get_hardware_address,
is_send_raw_packet_downstream_supported,
is_packet_capture_supported,
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
index ae0de79..c9d2527 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/CarrierConfigRule.kt
@@ -18,20 +18,28 @@
import android.Manifest.permission.MODIFY_PHONE_STATE
import android.Manifest.permission.READ_PHONE_STATE
+import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.PackageManager
import android.os.ConditionVariable
+import android.os.ParcelFileDescriptor
import android.os.PersistableBundle
+import android.os.Process
import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
+import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.modules.utils.build.SdkLevel.isAtLeastU
+import com.android.modules.utils.build.SdkLevel
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
+import java.security.MessageDigest
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.junit.rules.TestRule
@@ -46,12 +54,20 @@
* configuration automatically on teardown.
*/
class CarrierConfigRule : TestRule {
+ private val HEX_CHARS: CharArray = charArrayOf(
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ )
+
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ private val uiAutomation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation }
private val ccm by lazy { context.getSystemService(CarrierConfigManager::class.java) }
// Map of (subId) -> (original values of overridden settings)
private val originalConfigs = mutableMapOf<Int, PersistableBundle>()
+ // Map of (subId) -> (original values of carrier service package)
+ private val originalCarrierServicePackages = mutableMapOf<Int, String?>()
+
override fun apply(base: Statement, description: Description): Statement {
return CarrierConfigStatement(base, description)
}
@@ -118,6 +134,177 @@
}
}
+ private fun runShellCommand(cmd: String) {
+ val fd: ParcelFileDescriptor = uiAutomation.executeShellCommand(cmd)
+ fd.close() // Don't care about the output.
+ }
+
+ /**
+ * Converts a byte array into a String of hexadecimal characters.
+ *
+ * @param bytes an array of bytes
+ * @return hex string representation of bytes array
+ */
+ private fun bytesToHexString(bytes: ByteArray?): String? {
+ if (bytes == null) return null
+
+ val ret = StringBuilder(2 * bytes.size)
+
+ for (i in bytes.indices) {
+ var b: Int
+ b = 0x0f and (bytes[i].toInt() shr 4)
+ ret.append(HEX_CHARS[b])
+ b = 0x0f and bytes[i].toInt()
+ ret.append(HEX_CHARS[b])
+ }
+
+ return ret.toString()
+ }
+
+ private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw UnsupportedOperationException(
+ "Acquiring carrier privilege requires at least T SDK"
+ )
+ }
+
+ fun getCertHash(): String {
+ val pkgInfo = context.packageManager.getPackageInfo(
+ context.opPackageName,
+ PackageManager.GET_SIGNATURES
+ )
+ val digest = MessageDigest.getInstance("SHA-256")
+ val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
+ return bytesToHexString(certHash)!!
+ }
+
+ val tm = context.getSystemService(TelephonyManager::class.java)!!
+
+ val cv = ConditionVariable()
+ val cpb = PrivilegeWaiterCallback(cv)
+ // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
+ // T. This means the lambda will compile as a private method of this class taking a
+ // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
+ // including private methods, this would fail with a link error when running on S-.
+ // To solve this, make the lambda serializable, which causes the compiler to emit a
+ // synthetic class instead of a synthetic method.
+ tryTest @JvmSerializableLambda {
+ val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+ }
+ // Wait for the callback to be registered
+ assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+ "Can't register CarrierPrivilegesCallback")
+ if (cpb.hasPrivilege == hold) {
+ if (hold) {
+ Log.w(TAG, "Package ${context.opPackageName} already is privileged")
+ } else {
+ Log.w(TAG, "Package ${context.opPackageName} already isn't privileged")
+ }
+ return@tryTest
+ }
+ if (hold) {
+ addConfigOverrides(subId, PersistableBundle().also {
+ it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+ arrayOf(getCertHash()))
+ })
+ } else {
+ cleanUpNow()
+ }
+ } cleanup @JvmSerializableLambda {
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.unregisterCarrierPrivilegesCallback(cpb)
+ }
+ }
+ }
+
+ /**
+ * Acquires carrier privilege on the given subscription ID.
+ */
+ fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
+
+ /**
+ * Drops carrier privilege from the given subscription ID.
+ */
+ fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
+
+ /**
+ * Sets the carrier service package override for the given subscription ID. A null argument will
+ * clear any previously-set override.
+ */
+ fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw UnsupportedOperationException(
+ "Setting carrier service package override requires at least U SDK"
+ )
+ }
+
+ val tm = context.getSystemService(TelephonyManager::class.java)!!
+
+ val cv = ConditionVariable()
+ val cpb = CarrierServiceChangedWaiterCallback(cv)
+ // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
+ // T. This means the lambda will compile as a private method of this class taking a
+ // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
+ // including private methods, this would fail with a link error when running on S-.
+ // To solve this, make the lambda serializable, which causes the compiler to emit a
+ // synthetic class instead of a synthetic method.
+ tryTest @JvmSerializableLambda {
+ val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
+ }
+ // Wait for the callback to be registered
+ assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+ "Can't register CarrierPrivilegesCallback")
+ if (cpb.pkgName == pkg) {
+ Log.w(TAG, "Carrier service package was already $pkg")
+ return@tryTest
+ }
+ if (!originalCarrierServicePackages.contains(subId)) {
+ originalCarrierServicePackages.put(subId, cpb.pkgName)
+ }
+ cv.close()
+ runAsShell(MODIFY_PHONE_STATE) {
+ if (null == pkg) {
+ // There is a bug in clear-carrier-service-package-override where not adding
+ // the -s argument will use the wrong slot index : b/299604822
+ runShellCommand("cmd phone clear-carrier-service-package-override" +
+ " -s $subId")
+ } else {
+ runShellCommand("cmd phone set-carrier-service-package-override $pkg" +
+ " -s $subId")
+ }
+ }
+ assertTrue(cv.block(CARRIER_CONFIG_CHANGE_TIMEOUT_MS),
+ "Can't modify carrier service package")
+ } cleanup @JvmSerializableLambda {
+ runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
+ tm.unregisterCarrierPrivilegesCallback(cpb)
+ }
+ }
+ }
+
+ private class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
+ CarrierPrivilegesCallback {
+ var hasPrivilege = false
+ override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
+ hasPrivilege = uids.contains(Process.myUid())
+ cv.open()
+ }
+ }
+
+ private class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
+ CarrierPrivilegesCallback {
+ var pkgName: String? = null
+ override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
+ override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
+ this.pkgName = pkgName
+ cv.open()
+ }
+ }
+
/**
* Cleanup overrides that were added by the test case.
*
@@ -138,6 +325,10 @@
}
originalConfigs.clear()
}
+ originalCarrierServicePackages.forEach { (subId, pkg) ->
+ setCarrierServicePackageOverride(subId, pkg)
+ }
+ originalCarrierServicePackages.clear()
}
}
@@ -145,7 +336,7 @@
subId: Int,
keys: Set<String>
): PersistableBundle {
- return if (isAtLeastU()) {
+ return if (SdkLevel.isAtLeastU()) {
// This method is U+
getConfigForSubId(subId, *keys.toTypedArray())
} else {
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
index 2552aa3..33b3838 100644
--- a/staticlibs/testutils/host/python/apf_test_base.py
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -60,10 +60,10 @@
self.client_ipv4_addresses = apf_utils.get_ipv4_addresses(
self.clientDevice, self.client_iface_name
)
- self.server_ipv6_addresses = apf_utils.get_ipv6_addresses(
+ self.server_ipv6_addresses = apf_utils.get_non_tentative_ipv6_addresses(
self.serverDevice, self.server_iface_name
)
- self.client_ipv6_addresses = apf_utils.get_ipv6_addresses(
+ self.client_ipv6_addresses = apf_utils.get_non_tentative_ipv6_addresses(
self.clientDevice, self.client_iface_name
)
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index c2ad18e..1648d36 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -116,12 +116,12 @@
else:
return []
-def get_ipv6_addresses(
+def get_non_tentative_ipv6_addresses(
ad: android_device.AndroidDevice, iface_name: str
) -> list[str]:
- """Retrieves the IPv6 addresses of a given interface on an Android device.
+ """Retrieves the non-tentative IPv6 addresses of a given interface on an Android device.
- This function executes an ADB shell command (`ip -6 address show`) to get the
+ This function executes an ADB shell command (`ip -6 address show -tentative`) to get the
network interface information and extracts the IPv6 address from the output.
If devices have no IPv6 address, raise PatternNotFoundException.
@@ -139,7 +139,7 @@
# valid_lft forever preferred_lft forever
# inet6 fe80::1233:aadb:3d32:1234/64 scope link
# valid_lft forever preferred_lft forever
- output = adb_utils.adb_shell(ad, f"ip -6 address show {iface_name}")
+ output = adb_utils.adb_shell(ad, f"ip -6 address show -tentative {iface_name}")
pattern = r"inet6\s+([0-9a-fA-F:]+)\/\d+"
matches = re.findall(pattern, output)
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 5e035a2..6dc23ff 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -15,7 +15,6 @@
*/
package android.net.cts
-import android.Manifest.permission.MODIFY_PHONE_STATE
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
import android.app.Instrumentation
@@ -80,12 +79,10 @@
import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
import android.net.wifi.WifiInfo
import android.os.Build
-import android.os.ConditionVariable
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
-import android.os.PersistableBundle
import android.os.Process
import android.os.SystemClock
import android.platform.test.annotations.AppModeFull
@@ -94,19 +91,15 @@
import android.system.OsConstants.IPPROTO_TCP
import android.system.OsConstants.IPPROTO_UDP
import android.system.OsConstants.SOCK_DGRAM
-import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.CarrierPrivilegesCallback
import android.telephony.data.EpsBearerQosSessionAttributes
import android.util.ArraySet
import android.util.DebugUtils.valueToString
-import android.util.Log
import androidx.test.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.ThrowingSupplier
-import com.android.compatibility.common.util.UiccUtil
import com.android.modules.utils.build.SdkLevel
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.NetworkStackConstants.ETHER_MTU
@@ -151,7 +144,6 @@
import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer
-import java.security.MessageDigest
import java.time.Duration
import java.util.Arrays
import java.util.Random
@@ -708,102 +700,6 @@
doTestAllowedUids(transports, uid, expectUidsPresent, specifier, transportInfo)
}
- private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) {
- fun getCertHash(): String {
- val pkgInfo = realContext.packageManager.getPackageInfo(
- realContext.opPackageName,
- PackageManager.GET_SIGNATURES
- )
- val digest = MessageDigest.getInstance("SHA-256")
- val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray())
- return UiccUtil.bytesToHexString(certHash)!!
- }
-
- val tm = realContext.getSystemService(TelephonyManager::class.java)!!
-
- val cv = ConditionVariable()
- val cpb = PrivilegeWaiterCallback(cv)
- // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
- // T. This means the lambda will compile as a private method of this class taking a
- // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
- // including private methods, this would fail with a link error when running on S-.
- // To solve this, make the lambda serializable, which causes the compiler to emit a
- // synthetic class instead of a synthetic method.
- tryTest @JvmSerializableLambda {
- val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
- }
- // Wait for the callback to be registered
- assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
- if (cpb.hasPrivilege == hold) {
- if (hold) {
- Log.w(TAG, "Package ${realContext.opPackageName} already is privileged")
- } else {
- Log.w(TAG, "Package ${realContext.opPackageName} already isn't privileged")
- }
- return@tryTest
- }
- if (hold) {
- carrierConfigRule.addConfigOverrides(subId, PersistableBundle().also {
- it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
- arrayOf(getCertHash()))
- })
- } else {
- carrierConfigRule.cleanUpNow()
- }
- } cleanup @JvmSerializableLambda {
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.unregisterCarrierPrivilegesCallback(cpb)
- }
- }
- }
-
- private fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId)
- private fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId)
-
- private fun setCarrierServicePackageOverride(subId: Int, pkg: String?) {
- val tm = realContext.getSystemService(TelephonyManager::class.java)!!
-
- val cv = ConditionVariable()
- val cpb = CarrierServiceChangedWaiterCallback(cv)
- // The lambda below is capturing |cpb|, whose type inherits from a class that appeared in
- // T. This means the lambda will compile as a private method of this class taking a
- // PrivilegeWaiterCallback argument. As JUnit uses reflection to enumerate all methods
- // including private methods, this would fail with a link error when running on S-.
- // To solve this, make the lambda serializable, which causes the compiler to emit a
- // synthetic class instead of a synthetic method.
- tryTest @JvmSerializableLambda {
- val slotIndex = SubscriptionManager.getSlotIndex(subId)!!
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb)
- }
- // Wait for the callback to be registered
- assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback")
- if (cpb.pkgName == pkg) {
- Log.w(TAG, "Carrier service package was already $pkg")
- return@tryTest
- }
- cv.close()
- runAsShell(MODIFY_PHONE_STATE) {
- if (null == pkg) {
- // There is a bug is clear-carrier-service-package-override where not adding
- // the -s argument will use the wrong slot index : b/299604822
- runShellCommand("cmd phone clear-carrier-service-package-override" +
- " -s $subId")
- } else {
- // -s could set the subId, but this test works with the default subId.
- runShellCommand("cmd phone set-carrier-service-package-override $pkg")
- }
- }
- assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't modify carrier service package")
- } cleanup @JvmSerializableLambda {
- runAsShell(READ_PRIVILEGED_PHONE_STATE) @JvmSerializableLambda {
- tm.unregisterCarrierPrivilegesCallback(cpb)
- }
- }
- }
-
private fun String.execute() = runShellCommand(this).trim()
@Test
@@ -856,8 +752,8 @@
if (!SdkLevel.isAtLeastU()) return@tryTest
// Acquiring carrier privilege is necessary to override the carrier service package.
val defaultSlotIndex = SubscriptionManager.getSlotIndex(defaultSubId)
- acquireCarrierPrivilege(defaultSubId)
- setCarrierServicePackageOverride(defaultSubId, servicePackage)
+ carrierConfigRule.acquireCarrierPrivilege(defaultSubId)
+ carrierConfigRule.setCarrierServicePackageOverride(defaultSubId, servicePackage)
val actualServicePackage: String? = runAsShell(READ_PRIVILEGED_PHONE_STATE) {
tm.getCarrierServicePackageNameForLogicalSlot(defaultSlotIndex)
}
@@ -896,10 +792,6 @@
expectUidsPresent = false)
doTestAllowedUidsWithSubId(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI),
uid, expectUidsPresent = false)
- } cleanupStep {
- if (SdkLevel.isAtLeastU()) setCarrierServicePackageOverride(defaultSubId, null)
- } cleanup {
- if (SdkLevel.isAtLeastU()) dropCarrierPrivilege(defaultSubId)
}
}
@@ -2001,25 +1893,3 @@
doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN))
}
}
-
-// Subclasses of CarrierPrivilegesCallback can't be inline, or they'll be compiled as
-// inner classes of the test class and will fail resolution on R as the test harness
-// uses reflection to list all methods and classes
-class PrivilegeWaiterCallback(private val cv: ConditionVariable) :
- CarrierPrivilegesCallback {
- var hasPrivilege = false
- override fun onCarrierPrivilegesChanged(p: MutableSet<String>, uids: MutableSet<Int>) {
- hasPrivilege = uids.contains(Process.myUid())
- cv.open()
- }
-}
-
-class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) :
- CarrierPrivilegesCallback {
- var pkgName: String? = null
- override fun onCarrierPrivilegesChanged(p: MutableSet<String>, u: MutableSet<Int>) {}
- override fun onCarrierServiceChanged(pkgName: String?, uid: Int) {
- this.pkgName = pkgName
- cv.open()
- }
-}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index ff0e2c1..a96d06e 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -16,6 +16,7 @@
package com.android.server.thread;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
import static com.android.server.thread.ThreadPersistentSettings.KEY_COUNTRY_CODE;
import android.annotation.Nullable;
@@ -28,11 +29,13 @@
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
+import android.location.LocationListener;
import android.location.LocationManager;
import android.net.thread.IOperationReceiver;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback;
import android.os.Build;
+import android.os.Bundle;
import android.sysprop.ThreadNetworkProperties;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -115,6 +118,13 @@
new ArrayMap();
private final ThreadPersistentSettings mPersistentSettings;
+ @Nullable private LocationListener mLocationListener;
+ @Nullable private WifiCountryCodeCallback mWifiCountryCodeCallback;
+ @Nullable private BroadcastReceiver mTelephonyBroadcastReceiver;
+
+ /** Indicates whether the Thread co-processor supports setting the country code. */
+ private boolean mIsCpSettingCountryCodeSupported = true;
+
@Nullable private CountryCodeInfo mCurrentCountryCodeInfo;
@Nullable private CountryCodeInfo mLocationCountryCodeInfo;
@Nullable private CountryCodeInfo mOverrideCountryCodeInfo;
@@ -267,13 +277,40 @@
updateCountryCode(false /* forceUpdate */);
}
+ private synchronized void unregisterAllCountryCodeCallbacks() {
+ unregisterGeocoderCountryCodeCallback();
+ unregisterWifiCountryCodeCallback();
+ unregisterTelephonyCountryCodeCallback();
+ }
+
private synchronized void registerGeocoderCountryCodeCallback() {
if ((mGeocoder != null) && isLocationUseForCountryCodeEnabled()) {
+ mLocationListener =
+ new LocationListener() {
+ public void onLocationChanged(Location location) {
+ setCountryCodeFromGeocodingLocation(location);
+ }
+
+ // not used yet
+ public void onProviderDisabled(String provider) {}
+
+ public void onProviderEnabled(String provider) {}
+
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+ };
+
mLocationManager.requestLocationUpdates(
LocationManager.PASSIVE_PROVIDER,
TIME_BETWEEN_LOCATION_UPDATES_MS,
DISTANCE_BETWEEN_LOCALTION_UPDATES_METERS,
- location -> setCountryCodeFromGeocodingLocation(location));
+ mLocationListener);
+ }
+ }
+
+ private synchronized void unregisterGeocoderCountryCodeCallback() {
+ if (mLocationListener != null) {
+ mLocationManager.removeUpdates(mLocationListener);
+ mLocationListener = null;
}
}
@@ -313,8 +350,16 @@
private synchronized void registerWifiCountryCodeCallback() {
if (mWifiManager != null) {
+ mWifiCountryCodeCallback = new WifiCountryCodeCallback();
mWifiManager.registerActiveCountryCodeChangedCallback(
- r -> r.run(), new WifiCountryCodeCallback());
+ r -> r.run(), mWifiCountryCodeCallback);
+ }
+ }
+
+ private synchronized void unregisterWifiCountryCodeCallback() {
+ if ((mWifiManager != null) && (mWifiCountryCodeCallback != null)) {
+ mWifiManager.unregisterActiveCountryCodeChangedCallback(mWifiCountryCodeCallback);
+ mWifiCountryCodeCallback = null;
}
}
@@ -353,7 +398,7 @@
return;
}
- BroadcastReceiver broadcastReceiver =
+ mTelephonyBroadcastReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -377,11 +422,18 @@
};
mContext.registerReceiver(
- broadcastReceiver,
+ mTelephonyBroadcastReceiver,
new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED),
Context.RECEIVER_EXPORTED);
}
+ private synchronized void unregisterTelephonyCountryCodeCallback() {
+ if (mTelephonyBroadcastReceiver != null) {
+ mContext.unregisterReceiver(mTelephonyBroadcastReceiver);
+ mTelephonyBroadcastReceiver = null;
+ }
+ }
+
private synchronized void updateTelephonyCountryCodeFromSimCard() {
List<SubscriptionInfo> subscriptionInfoList =
mSubscriptionManager.getActiveSubscriptionInfoList();
@@ -520,6 +572,11 @@
@Override
public void onError(int otError, String message) {
+ if (otError == ERROR_UNSUPPORTED_FEATURE) {
+ mIsCpSettingCountryCodeSupported = false;
+ unregisterAllCountryCodeCallbacks();
+ }
+
LOG.e(
"Error "
+ otError
@@ -546,6 +603,11 @@
return;
}
+ if (!mIsCpSettingCountryCodeSupported) {
+ LOG.i("Thread co-processor doesn't support setting the country code");
+ return;
+ }
+
LOG.i("Set country code: " + countryCodeInfo);
mThreadNetworkControllerService.setCountryCode(
countryCodeInfo.getCountryCode().toUpperCase(Locale.ROOT),
@@ -592,6 +654,7 @@
/** Dumps the current state of this ThreadNetworkCountryCode object. */
public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("---- Dump of ThreadNetworkCountryCode begin ----");
+ pw.println("mIsCpSettingCountryCodeSupported: " + mIsCpSettingCountryCodeSupported);
pw.println("mOverrideCountryCodeInfo : " + mOverrideCountryCodeInfo);
pw.println("mTelephonyCountryCodeSlotInfoMap: " + mTelephonyCountryCodeSlotInfoMap);
pw.println("mTelephonyCountryCodeInfo : " + mTelephonyCountryCodeInfo);
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
index 139f4c8..6eb9b50 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -17,6 +17,7 @@
package com.android.server.thread;
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
import static com.android.server.thread.ThreadPersistentSettings.KEY_COUNTRY_CODE;
@@ -109,6 +110,7 @@
private ThreadNetworkCountryCode mThreadNetworkCountryCode;
private boolean mErrorSetCountryCode;
+ private boolean mErrorUnsupportedFeatureSetCountryCode;
@Captor private ArgumentCaptor<LocationListener> mLocationListenerCaptor;
@Captor private ArgumentCaptor<Geocoder.GeocodeListener> mGeocodeListenerCaptor;
@@ -143,6 +145,10 @@
if (mErrorSetCountryCode) {
cb.onError(ERROR_INTERNAL_ERROR, new String("Invalid country code"));
+ } else if (mErrorUnsupportedFeatureSetCountryCode) {
+ cb.onError(
+ ERROR_UNSUPPORTED_FEATURE,
+ new String("Setting country code is not supported"));
} else {
cb.onSuccess();
}
@@ -453,6 +459,39 @@
}
@Test
+ public void setCountryCodeNotSupported_returnUnsupportedFeatureError_countryCodeNotSetAgain() {
+ mThreadNetworkCountryCode.initialize();
+ assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
+
+ mErrorUnsupportedFeatureSetCountryCode = true;
+ mThreadNetworkCountryCode.setOverrideCountryCode(TEST_COUNTRY_CODE_CN);
+ verify(mThreadNetworkControllerService)
+ .setCountryCode(eq(TEST_COUNTRY_CODE_CN), mOperationReceiverCaptor.capture());
+
+ mThreadNetworkCountryCode.setOverrideCountryCode(TEST_COUNTRY_CODE_US);
+ verifyNoMoreInteractions(mThreadNetworkControllerService);
+
+ assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
+ }
+
+ @Test
+ public void setCountryCodeNotSupported_returnUnsupportedFeatureError_unregisterAllCallbacks() {
+ mThreadNetworkCountryCode.initialize();
+ assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(DEFAULT_COUNTRY_CODE);
+
+ mErrorUnsupportedFeatureSetCountryCode = true;
+ mThreadNetworkCountryCode.setOverrideCountryCode(TEST_COUNTRY_CODE_CN);
+ verify(mThreadNetworkControllerService)
+ .setCountryCode(eq(TEST_COUNTRY_CODE_CN), mOperationReceiverCaptor.capture());
+
+ verify(mLocationManager).removeUpdates(mLocationListenerCaptor.capture());
+ verify(mWifiManager)
+ .unregisterActiveCountryCodeChangedCallback(
+ mWifiCountryCodeReceiverCaptor.capture());
+ verify(mContext).unregisterReceiver(mTelephonyCountryCodeReceiverCaptor.capture());
+ }
+
+ @Test
public void settingsCountryCode_settingsCountryCodeIsActive_settingsCountryCodeIsUsed() {
when(mPersistentSettings.get(KEY_COUNTRY_CODE)).thenReturn(TEST_COUNTRY_CODE_CN);
mThreadNetworkCountryCode.initialize();
@@ -468,6 +507,7 @@
mThreadNetworkCountryCode.dump(new FileDescriptor(), printWriter, null);
String outputString = stringWriter.toString();
+ assertThat(outputString).contains("mIsCpSettingCountryCodeSupported");
assertThat(outputString).contains("mOverrideCountryCodeInfo");
assertThat(outputString).contains("mTelephonyCountryCodeSlotInfoMap");
assertThat(outputString).contains("mTelephonyCountryCodeInfo");