Merge "Revert^2 "Baseline one more Lint FlaggedApi violation"" into main
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 310db9a..c2e4a90 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -40,7 +40,17 @@
 import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED;
+import static android.net.ConnectivityManager.CALLBACK_AVAILABLE;
+import static android.net.ConnectivityManager.CALLBACK_BLK_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_CAP_CHANGED;
 import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_LOSING;
+import static android.net.ConnectivityManager.CALLBACK_LOST;
+import static android.net.ConnectivityManager.CALLBACK_PRECHECK;
+import static android.net.ConnectivityManager.CALLBACK_RESUMED;
+import static android.net.ConnectivityManager.CALLBACK_SUSPENDED;
+import static android.net.ConnectivityManager.CALLBACK_UNAVAIL;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
@@ -5364,7 +5374,7 @@
         // by other networks that are already connected. Perhaps that can be done by
         // sending all CALLBACK_LOST messages (for requests, not listens) at the end
         // of rematchAllNetworksAndRequests
-        notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
+        notifyNetworkCallbacks(nai, CALLBACK_LOST);
         mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK);
 
         mQosCallbackTracker.handleNetworkReleased(nai.network);
@@ -5486,8 +5496,7 @@
             // correctly contains null as an upstream.
             if (sendCallbacks) {
                 nri.setSatisfier(null, null);
-                notifyNetworkCallbacks(local,
-                        ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+                notifyNetworkCallbacks(local, CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
             }
         }
 
@@ -5862,8 +5871,7 @@
             log("releasing " + nri.mRequests.get(0) + " (timeout)");
         }
         handleRemoveNetworkRequest(nri);
-        callCallbackForRequest(
-                nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+        callCallbackForRequest(nri, null, CALLBACK_UNAVAIL, 0);
     }
 
     private void handleReleaseNetworkRequest(@NonNull final NetworkRequest request,
@@ -5879,7 +5887,7 @@
         }
         handleRemoveNetworkRequest(nri);
         if (callOnUnavailable) {
-            callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+            callCallbackForRequest(nri, null, CALLBACK_UNAVAIL, 0);
         }
     }
 
@@ -7035,7 +7043,7 @@
         // should have its link properties fixed up for PAC proxies.
         mProxyTracker.updateDefaultNetworkProxyPortForPAC(nai.linkProperties, nai.network);
         if (nai.everConnected()) {
-            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_IP_CHANGED);
+            notifyNetworkCallbacks(nai, CALLBACK_IP_CHANGED);
         }
     }
 
@@ -7792,10 +7800,52 @@
                     + " callback flags: " + mCallbackFlags
                     + " order: " + mPreferenceOrder
                     + " isUidTracked: " + mUidTrackedForBlockedStatus
-                    + " declaredMethods: 0x" + Integer.toHexString(mDeclaredMethodsFlags);
+                    + " declaredMethods: " + declaredMethodsFlagsToString(mDeclaredMethodsFlags);
         }
     }
 
+    /**
+     * Get a readable String for a bitmask of declared methods.
+     */
+    @VisibleForTesting
+    public static String declaredMethodsFlagsToString(int flags) {
+        if (flags == DECLARED_METHODS_NONE) {
+            return "NONE";
+        }
+        if (flags == DECLARED_METHODS_ALL) {
+            return "ALL";
+        }
+        final StringBuilder sb = new StringBuilder();
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_PRECHECK, "PRECHK", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_AVAILABLE, "AVAIL", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOSING, "LOSING", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOST, "LOST", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_UNAVAIL, "UNAVAIL", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_CAP_CHANGED, "NC", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_IP_CHANGED, "LP", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_SUSPENDED, "SUSP", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_RESUMED, "RESUME", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_BLK_CHANGED, "BLK", sb);
+        flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOCAL_NETWORK_INFO_CHANGED,
+                "LOCALINF", sb);
+        if (flags != 0) {
+            sb.append("|0x").append(Integer.toHexString(flags));
+        }
+        return sb.toString();
+    }
+
+    private static int maybeAppendDeclaredMethod(int declaredMethodsFlags,
+            int callbackId, String callbackName, @NonNull StringBuilder builder) {
+        final int callbackFlag = 1 << callbackId;
+        if ((declaredMethodsFlags & callbackFlag) != 0) {
+            if (builder.length() > 0) {
+                builder.append('|');
+            }
+            builder.append(callbackName);
+        }
+        return declaredMethodsFlags & ~callbackFlag;
+    }
+
     // Keep backward compatibility since the ServiceSpecificException is used by
     // the API surface, see {@link ConnectivityManager#convertServiceException}.
     public static class RequestInfoPerUidCounter extends PerUidCounter {
@@ -9082,7 +9132,7 @@
             }
             networkAgent.networkMonitor().notifyLinkPropertiesChanged(
                     new LinkProperties(newLp, true /* parcelSensitiveFields */));
-            notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
+            notifyNetworkCallbacks(networkAgent, CALLBACK_IP_CHANGED);
         }
 
         mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
@@ -9578,8 +9628,7 @@
         if (prevSuspended != suspended) {
             // TODO (b/73132094) : remove this call once the few users of onSuspended and
             // onResumed have been removed.
-            notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED
-                    : ConnectivityManager.CALLBACK_RESUMED);
+            notifyNetworkCallbacks(nai, suspended ? CALLBACK_SUSPENDED : CALLBACK_RESUMED);
         }
         if (prevSuspended != suspended || prevRoaming != roaming) {
             // updateNetworkInfo will mix in the suspended info from the capabilities and
@@ -9666,7 +9715,7 @@
             // If the requestable capabilities have changed or the score changed, we can't have been
             // called by rematchNetworkAndRequests, so it's safe to start a rematch.
             rematchAllNetworksAndRequests();
-            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+            notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
         }
         updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc);
 
@@ -9808,7 +9857,7 @@
                 // But here there is no new request, so the rematch won't see anything. Send
                 // callbacks to apps now to tell them about the loss of upstream.
                 notifyNetworkCallbacks(nai,
-                        ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+                        CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
                 return;
             }
         }
@@ -10114,7 +10163,7 @@
 
     private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
             int notificationType) {
-        if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
+        if (notificationType == CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
             Intent intent = new Intent();
             intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
             // If apps could file multi-layer requests with PendingIntents, they'd need to know
@@ -10207,13 +10256,13 @@
         final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
         putParcelable(bundle, nrForCallback);
         Message msg = Message.obtain();
-        if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
+        if (notificationType != CALLBACK_UNAVAIL) {
             putParcelable(bundle, networkAgent.network);
         }
         final boolean includeLocationSensitiveInfo =
                 (nri.mCallbackFlags & NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) != 0;
         switch (notificationType) {
-            case ConnectivityManager.CALLBACK_AVAILABLE: {
+            case CALLBACK_AVAILABLE: {
                 final NetworkCapabilities nc =
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
                                 networkCapabilitiesRestrictedForCallerPermissions(
@@ -10232,11 +10281,11 @@
                 msg.arg1 = arg1;
                 break;
             }
-            case ConnectivityManager.CALLBACK_LOSING: {
+            case CALLBACK_LOSING: {
                 msg.arg1 = arg1;
                 break;
             }
-            case ConnectivityManager.CALLBACK_CAP_CHANGED: {
+            case CALLBACK_CAP_CHANGED: {
                 // networkAgent can't be null as it has been accessed a few lines above.
                 final NetworkCapabilities netCap =
                         networkCapabilitiesRestrictedForCallerPermissions(
@@ -10249,17 +10298,17 @@
                                 nri.mCallingAttributionTag));
                 break;
             }
-            case ConnectivityManager.CALLBACK_IP_CHANGED: {
+            case CALLBACK_IP_CHANGED: {
                 putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
                         networkAgent.linkProperties, nri.mPid, nri.mUid));
                 break;
             }
-            case ConnectivityManager.CALLBACK_BLK_CHANGED: {
+            case CALLBACK_BLK_CHANGED: {
                 maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
                 msg.arg1 = arg1;
                 break;
             }
-            case ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
+            case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
                 if (!networkAgent.isLocalNetwork()) {
                     Log.wtf(TAG, "Callback for local info for a non-local network");
                     return;
@@ -10528,7 +10577,7 @@
     private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
         // For consistency with previous behaviour, send onLost callbacks before onAvailable.
         processNewlyLostListenRequests(nai);
-        notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+        notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
         processNewlySatisfiedListenRequests(nai);
     }
 
@@ -10541,7 +10590,7 @@
             if (!nr.isListen()) continue;
             if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {
                 nai.removeRequest(nr.requestId);
-                callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
+                callCallbackForRequest(nri, nai, CALLBACK_LOST, 0);
             }
         }
     }
@@ -10873,7 +10922,7 @@
                 notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
             } else {
                 callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork,
-                        ConnectivityManager.CALLBACK_LOST, 0);
+                        CALLBACK_LOST, 0);
             }
         }
 
@@ -10917,7 +10966,7 @@
         if (null != localInfoChangedAgents) {
             for (final NetworkAgentInfo nai : localInfoChangedAgents) {
                 notifyNetworkCallbacks(nai,
-                        ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+                        CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
             }
         }
 
@@ -10960,7 +11009,7 @@
         if (Objects.equals(nai.networkCapabilities, newNc)) return;
         updateNetworkPermissions(nai, newNc);
         nai.getAndSetNetworkCapabilities(newNc);
-        notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+        notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
     }
 
     private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
@@ -11329,7 +11378,7 @@
             rematchAllNetworksAndRequests();
 
             // This has to happen after matching the requests, because callbacks are just requests.
-            notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+            notifyNetworkCallbacks(networkAgent, CALLBACK_PRECHECK);
         } else if (state == NetworkInfo.State.DISCONNECTED) {
             networkAgent.disconnect();
             if (networkAgent.isVPN()) {
@@ -11362,7 +11411,7 @@
     protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {
         mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
         if (nri.mPendingIntent != null) {
-            sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);
+            sendPendingIntentForRequest(nri, nai, CALLBACK_AVAILABLE);
             // Attempt no subsequent state pushes where intents are involved.
             return;
         }
@@ -11370,14 +11419,14 @@
         final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
         final boolean metered = nai.networkCapabilities.isMetered();
         final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
-        callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
+        callCallbackForRequest(nri, nai, CALLBACK_AVAILABLE,
                 getBlockedState(nri.mAsUid, blockedReasons, metered, vpnBlocked));
     }
 
     // Notify the requests on this NAI that the network is now lingered.
     private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) {
         final int lingerTime = (int) (nai.getInactivityExpiry() - now);
-        notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
+        notifyNetworkCallbacks(nai, CALLBACK_LOSING, lingerTime);
     }
 
     private int getPermissionBlockedState(final int uid, final int reasons) {
@@ -11440,7 +11489,7 @@
             final int newBlockedState = getBlockedState(
                     nri.mAsUid, blockedReasons, newMetered, newVpnBlocked);
             if (oldBlockedState != newBlockedState) {
-                callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+                callCallbackForRequest(nri, nai, CALLBACK_BLK_CHANGED,
                         newBlockedState);
             }
         }
@@ -11467,7 +11516,7 @@
                 NetworkRequest nr = nai.requestAt(i);
                 NetworkRequestInfo nri = mNetworkRequests.get(nr);
                 if (nri != null && nri.mAsUid == uid) {
-                    callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+                    callCallbackForRequest(nri, nai, CALLBACK_BLK_CHANGED,
                             newBlockedState);
                 }
             }
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index e9f8858..3186033 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -76,7 +76,14 @@
     test_suites: [
         "general-tests",
     ],
+    // MoblyBinaryHostTest doesn't support unit_test.
     test_options: {
-        unit_test: true,
+        unit_test: false,
+    },
+    // Needed for applying VirtualEnv.
+    version: {
+        py3: {
+            embedded_launcher: false,
+        },
     },
 }
diff --git a/staticlibs/tests/unit/host/python/adb_utils_test.py b/staticlibs/tests/unit/host/python/adb_utils_test.py
index b75d464..8fcca37 100644
--- a/staticlibs/tests/unit/host/python/adb_utils_test.py
+++ b/staticlibs/tests/unit/host/python/adb_utils_test.py
@@ -12,15 +12,21 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import unittest
 from unittest.mock import MagicMock, patch
+from absl.testing import parameterized
+from mobly import asserts
+from mobly import base_test
+from mobly import config_parser
 from net_tests_utils.host.python import adb_utils
 from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
 
 
-class TestAdbUtils(unittest.TestCase):
+class TestAdbUtils(base_test.BaseTestClass, parameterized.TestCase):
 
-  def setUp(self):
+  def __init__(self, configs: config_parser.TestRunConfig):
+    super().__init__(configs)
+
+  def setup_test(self):
     self.mock_ad = MagicMock()  # Mock Android device object
     self.mock_ad.log = MagicMock()
     self.mock_ad.adb.shell.return_value = b""  # Default empty return for shell
@@ -49,25 +55,21 @@
   @patch("net_tests_utils.host.python.adb_utils._get_screen_state")
   def test_set_screen_state_failure(self, mock_get_screen_state):
     mock_get_screen_state.return_value = False  # State doesn't change
-    with self.assertRaises(UnexpectedBehaviorError):
+    with asserts.assert_raises(UnexpectedBehaviorError):
       adb_utils._set_screen_state(self.mock_ad, True)
 
+  @parameterized.parameters(
+      ("Awake", True),
+      ("Asleep", False),
+      ("Dozing", False),
+      ("SomeOtherState", False),
+  )  # Declare inputs for state_str and expected_result.
   @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
-  def test_get_screen_state(self, mock_get_value):
-    # Test cases for different return values of get_value_of_key_from_dumpsys
-    # TODO: Make it parameterized.
-    test_cases = [
-        ("Awake", True),
-        ("Asleep", False),
-        ("Dozing", False),
-        ("SomeOtherState", False),
-    ]
-
-    for state_str, expected_result in test_cases:
-      mock_get_value.return_value = state_str
-      self.assertEqual(
-          adb_utils._get_screen_state(self.mock_ad), expected_result
-      )
+  def test_get_screen_state(self, state_str, expected_result, mock_get_value):
+    mock_get_value.return_value = state_str
+    asserts.assert_equal(
+        adb_utils._get_screen_state(self.mock_ad), expected_result
+    )
 
   def test_get_value_of_key_from_dumpsys(self):
     self.mock_ad.adb.shell.return_value = (
@@ -76,36 +78,34 @@
     result = adb_utils.get_value_of_key_from_dumpsys(
         self.mock_ad, "power", "mWakefulness"
     )
-    self.assertEqual(result, "Awake")
+    asserts.assert_equal(result, "Awake")
 
+  @parameterized.parameters(
+      (True, ["true"]),
+      (False, ["false"]),
+      (
+          True,
+          ["false", "true"],
+      ),  # Expect True, get False which is unexpected, then get True
+      (
+          False,
+          ["true", "false"],
+      ),  # Expect False, get True which is unexpected, then get False
+  )  # Declare inputs for expected_state and returned_value
   @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
-  def test_expect_dumpsys_state_with_retry_success(self, mock_get_value):
-    # Test cases for different combinations of expected_state and get_value return value
-    # TODO: Make it parameterized.
-    test_cases = [
-        (True, ["true"]),  # Expect True, get True
-        (False, ["false"]),  # Expect False, get False
-        (
-            True,
-            ["false", "true"],
-        ),  # Expect True, get False which is unexpected, then get True
-        (
-            False,
-            ["true", "false"],
-        ),  # Expect False, get True which is unexpected, then get False
-    ]
-
-    for expected_state, returned_value in test_cases:
-      mock_get_value.side_effect = returned_value
-      # Verify the method returns and does not throw.
-      adb_utils.expect_dumpsys_state_with_retry(
-          self.mock_ad, "service", "key", expected_state, 0
-      )
+  def test_expect_dumpsys_state_with_retry_success(
+      self, expected_state, returned_value, mock_get_value
+  ):
+    mock_get_value.side_effect = returned_value
+    # Verify the method returns and does not throw.
+    adb_utils.expect_dumpsys_state_with_retry(
+        self.mock_ad, "service", "key", expected_state, 0
+    )
 
   @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
   def test_expect_dumpsys_state_with_retry_failure(self, mock_get_value):
     mock_get_value.return_value = "false"
-    with self.assertRaises(UnexpectedBehaviorError):
+    with asserts.assert_raises(UnexpectedBehaviorError):
       adb_utils.expect_dumpsys_state_with_retry(
           self.mock_ad, "service", "key", True, 0
       )
@@ -116,11 +116,7 @@
     mock_get_value.return_value = None
 
     # Expect the function to raise UnexpectedBehaviorError due to the exception
-    with self.assertRaises(UnexpectedBehaviorError):
+    with asserts.assert_raises(UnexpectedBehaviorError):
       adb_utils.expect_dumpsys_state_with_retry(
           self.mock_ad, "service", "key", True
       )
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index f7fb93b..8b390e3 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -12,18 +12,29 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import unittest
 from unittest.mock import MagicMock, patch
+from mobly import asserts
+from mobly import base_test
+from mobly import config_parser
+from mobly.controllers.android_device_lib.adb import AdbError
 from net_tests_utils.host.python.apf_utils import (
     PatternNotFoundException,
+    UnsupportedOperationException,
     get_apf_counter,
     get_apf_counters_from_dumpsys,
+    get_hardware_address,
+    send_broadcast_empty_ethercat_packet,
+    send_raw_packet_downstream,
 )
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
 
 
-class TestApfUtils(unittest.TestCase):
+class TestApfUtils(base_test.BaseTestClass):
 
-  def setUp(self):
+  def __init__(self, configs: config_parser.TestRunConfig):
+    super().__init__(configs)
+
+  def setup_test(self):
     self.mock_ad = MagicMock()  # Mock Android device object
 
   @patch("net_tests_utils.host.python.adb_utils.get_dumpsys_for_service")
@@ -36,9 +47,8 @@
     COUNTER_NAME1: 123
     COUNTER_NAME2: 456
 """
-    iface_name = "wlan0"
-    counters = get_apf_counters_from_dumpsys(self.mock_ad, iface_name)
-    self.assertEqual(counters, {"COUNTER_NAME1": 123, "COUNTER_NAME2": 456})
+    counters = get_apf_counters_from_dumpsys(self.mock_ad, "wlan0")
+    asserts.assert_equal(counters, {"COUNTER_NAME1": 123, "COUNTER_NAME2": 456})
 
   @patch("net_tests_utils.host.python.adb_utils.get_dumpsys_for_service")
   def test_get_apf_counters_from_dumpsys_exceptions(
@@ -58,7 +68,7 @@
 
     for dumpsys_output in test_cases:
       mock_get_dumpsys.return_value = dumpsys_output
-      with self.assertRaises(PatternNotFoundException):
+      with asserts.assert_raises(PatternNotFoundException):
         get_apf_counters_from_dumpsys(self.mock_ad, "wlan0")
 
   @patch("net_tests_utils.host.python.apf_utils.get_apf_counters_from_dumpsys")
@@ -68,10 +78,75 @@
         "COUNTER_NAME1": 123,
         "COUNTER_NAME2": 456,
     }
-    self.assertEqual(get_apf_counter(self.mock_ad, iface, "COUNTER_NAME1"), 123)
+    asserts.assert_equal(
+        get_apf_counter(self.mock_ad, iface, "COUNTER_NAME1"), 123
+    )
     # Not found
-    self.assertEqual(get_apf_counter(self.mock_ad, iface, "COUNTER_NAME3"), 0)
+    asserts.assert_equal(
+        get_apf_counter(self.mock_ad, iface, "COUNTER_NAME3"), 0
+    )
 
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_hardware_address_success(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = """
+46: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq ...
+ link/ether 72:05:77:82:21:e0 brd ff:ff:ff:ff:ff:ff
+"""
+    mac_address = get_hardware_address(self.mock_ad, "wlan0")
+    asserts.assert_equal(mac_address, "72:05:77:82:21:E0")
 
-if __name__ == "__main__":
-  unittest.main()
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_hardware_address_not_found(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = "Some output without MAC address"
+    with asserts.assert_raises(PatternNotFoundException):
+      get_hardware_address(self.mock_ad, "wlan0")
+
+  @patch("net_tests_utils.host.python.apf_utils.get_hardware_address")
+  @patch("net_tests_utils.host.python.apf_utils.send_raw_packet_downstream")
+  def test_send_broadcast_empty_ethercat_packet(
+      self,
+      mock_send_raw_packet_downstream: MagicMock,
+      mock_get_hardware_address: MagicMock,
+  ) -> None:
+    mock_get_hardware_address.return_value = "12:34:56:78:90:AB"
+    send_broadcast_empty_ethercat_packet(self.mock_ad, "eth0")
+    # Assuming you'll mock the packet construction part, verify calls to send_raw_packet_downstream.
+    mock_send_raw_packet_downstream.assert_called_once()
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_send_raw_packet_downstream_success(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = ""  # Successful command output
+    iface_name = "eth0"
+    packet_in_hex = "AABBCCDDEEFF"
+    send_raw_packet_downstream(self.mock_ad, iface_name, packet_in_hex)
+    mock_adb_shell.assert_called_once_with(
+        self.mock_ad,
+        "cmd network_stack send-raw-packet-downstream"
+        f" {iface_name} {packet_in_hex}",
+    )
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_send_raw_packet_downstream_failure(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = (  # Unexpected command output
+        "Any Unexpected Output"
+    )
+    with asserts.assert_raises(UnexpectedBehaviorError):
+      send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_send_raw_packet_downstream_unsupported(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.side_effect = AdbError(
+        cmd="", stdout="Unknown command", stderr="", ret_code=3
+    )
+    with asserts.assert_raises(UnsupportedOperationException):
+      send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
diff --git a/staticlibs/tests/unit/host/python/assert_utils_test.py b/staticlibs/tests/unit/host/python/assert_utils_test.py
index b970d14..7a33373 100644
--- a/staticlibs/tests/unit/host/python/assert_utils_test.py
+++ b/staticlibs/tests/unit/host/python/assert_utils_test.py
@@ -12,11 +12,12 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import unittest
+from mobly import asserts
+from mobly import base_test
 from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError, expect_with_retry
 
 
-class TestAssertUtils(unittest.TestCase):
+class TestAssertUtils(base_test.BaseTestClass):
 
   def test_predicate_succeed(self):
     """Test when the predicate becomes True within retries."""
@@ -28,12 +29,12 @@
       return call_count > 2  # True on the third call
 
     expect_with_retry(predicate, max_retries=5, retry_interval_sec=0)
-    self.assertEqual(call_count, 3)  # Ensure it was called exactly 3 times
+    asserts.assert_equal(call_count, 3)  # Ensure it was called exactly 3 times
 
   def test_predicate_failed(self):
     """Test when the predicate never becomes True."""
 
-    with self.assertRaises(UnexpectedBehaviorError):
+    with asserts.assert_raises(UnexpectedBehaviorError):
       expect_with_retry(
           predicate=lambda: False, max_retries=3, retry_interval_sec=0
       )
@@ -52,7 +53,9 @@
         max_retries=5,
         retry_interval_sec=0,
     )
-    self.assertFalse(retry_action_called)  # Assert retry_action was NOT called
+    asserts.assert_false(
+        retry_action_called, "retry_action called."
+    )  # Assert retry_action was NOT called
 
   def test_retry_action_not_called_failed(self):
     """Test that the retry_action is not called if the max_retries is reached."""
@@ -62,14 +65,16 @@
       nonlocal retry_action_called
       retry_action_called = True
 
-    with self.assertRaises(UnexpectedBehaviorError):
+    with asserts.assert_raises(UnexpectedBehaviorError):
       expect_with_retry(
           predicate=lambda: False,
           retry_action=retry_action,
           max_retries=1,
           retry_interval_sec=0,
       )
-    self.assertFalse(retry_action_called)  # Assert retry_action was NOT called
+    asserts.assert_false(
+        retry_action_called, "retry_action called."
+    )  # Assert retry_action was NOT called
 
   def test_retry_action_called(self):
     """Test that the retry_action is executed when provided."""
@@ -79,11 +84,11 @@
       nonlocal retry_action_called
       retry_action_called = True
 
-    with self.assertRaises(UnexpectedBehaviorError):
+    with asserts.assert_raises(UnexpectedBehaviorError):
       expect_with_retry(
           predicate=lambda: False,
           retry_action=retry_action,
           max_retries=2,
           retry_interval_sec=0,
       )
-    self.assertTrue(retry_action_called)
+    asserts.assert_true(retry_action_called, "retry_action not called.")
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
index c44f1a8..fa6a310 100644
--- a/staticlibs/tests/unit/host/python/run_tests.py
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -14,14 +14,22 @@
 
 """Main entrypoint for all of unittest."""
 
-import unittest
-
-# Import all unittest classes here, so it can be discovered by unittest module.
-# TODO: make the tests can be executed without manually import classes.
+import sys
 from host.python.adb_utils_test import TestAdbUtils
 from host.python.apf_utils_test import TestApfUtils
 from host.python.assert_utils_test import TestAssertUtils
+from mobly import suite_runner
 
 
 if __name__ == "__main__":
-  unittest.main()
+  # For MoblyBinaryHostTest, this entry point will be called twice:
+  # 1. List tests.
+  #   <mobly-par-file-name> -- --list_tests
+  # 2. Run tests.
+  #   <mobly-par-file-name> -- --config=<yaml-path> --device_serial=<device-serial> --log_path=<log-path>
+  # Strip the "--" since suite runner doesn't recognize it.
+  sys.argv.pop(1)
+  # TODO: make the tests can be executed without manually list classes.
+  suite_runner.run_suite(
+      [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
+  )
diff --git a/staticlibs/tests/unit/host/python/test_config.xml b/staticlibs/tests/unit/host/python/test_config.xml
index 26ee9e2..d3b200a 100644
--- a/staticlibs/tests/unit/host/python/test_config.xml
+++ b/staticlibs/tests/unit/host/python/test_config.xml
@@ -14,9 +14,11 @@
      limitations under the License.
 -->
 <configuration description="Config for NetworkStaticLibHostPythonTests">
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
-    <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest">
-        <option name="par-file-name" value="NetworkStaticLibHostPythonTests" />
-        <option name="test-timeout" value="3m" />
+    <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
+        <option name="dep-module" value="absl-py" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest" >
+        <option name="mobly-par-file-name" value="NetworkStaticLibHostPythonTests" />
+        <option name="mobly-test-timeout" value="3m" />
     </test>
 </configuration>
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 66362d4..ae43c15 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -44,13 +44,18 @@
 object ANY_NETWORK : Network(-2)
 fun anyNetwork() = ANY_NETWORK
 
-open class RecorderCallback private constructor(
-    private val backingRecord: ArrayTrackRecord<CallbackEntry>
-) : NetworkCallback() {
-    public constructor() : this(ArrayTrackRecord())
-    protected constructor(src: RecorderCallback?) : this(src?.backingRecord ?: ArrayTrackRecord())
+private val DEFAULT_TAG = RecorderCallback::class.simpleName
+    ?: fail("Could not determine class name")
 
-    private val TAG = this::class.simpleName
+open class RecorderCallback private constructor(
+    private val backingRecord: ArrayTrackRecord<CallbackEntry>,
+    val logTag: String
+) : NetworkCallback() {
+    public constructor(logTag: String = DEFAULT_TAG) : this(ArrayTrackRecord(), logTag)
+    protected constructor(src: RecorderCallback?, logTag: String) : this(
+        src?.backingRecord ?: ArrayTrackRecord(),
+        logTag
+    )
 
     sealed class CallbackEntry {
         // To get equals(), hashcode(), componentN() etc for free, the child classes of
@@ -123,7 +128,7 @@
     val mark get() = history.mark
 
     override fun onAvailable(network: Network) {
-        Log.d(TAG, "onAvailable $network")
+        Log.d(logTag, "onAvailable $network")
         history.add(Available(network))
     }
 
@@ -131,22 +136,22 @@
     // expect the callbacks not to record this, do not listen to PreCheck here.
 
     override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
-        Log.d(TAG, "onCapabilitiesChanged $network $caps")
+        Log.d(logTag, "onCapabilitiesChanged $network $caps")
         history.add(CapabilitiesChanged(network, caps))
     }
 
     override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
-        Log.d(TAG, "onLinkPropertiesChanged $network $lp")
+        Log.d(logTag, "onLinkPropertiesChanged $network $lp")
         history.add(LinkPropertiesChanged(network, lp))
     }
 
     override fun onLocalNetworkInfoChanged(network: Network, info: LocalNetworkInfo) {
-        Log.d(TAG, "onLocalNetworkInfoChanged $network $info")
+        Log.d(logTag, "onLocalNetworkInfoChanged $network $info")
         history.add(LocalInfoChanged(network, info))
     }
 
     override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
-        Log.d(TAG, "onBlockedStatusChanged $network $blocked")
+        Log.d(logTag, "onBlockedStatusChanged $network $blocked")
         history.add(BlockedStatus(network, blocked))
     }
 
@@ -154,27 +159,27 @@
     // fun onBlockedStatusChanged(network: Network, blocked: Int) {
     // because on S, that needs to be "override fun", and on R, that cannot be "override fun".
     override fun onNetworkSuspended(network: Network) {
-        Log.d(TAG, "onNetworkSuspended $network $network")
+        Log.d(logTag, "onNetworkSuspended $network $network")
         history.add(Suspended(network))
     }
 
     override fun onNetworkResumed(network: Network) {
-        Log.d(TAG, "$network onNetworkResumed $network")
+        Log.d(logTag, "$network onNetworkResumed $network")
         history.add(Resumed(network))
     }
 
     override fun onLosing(network: Network, maxMsToLive: Int) {
-        Log.d(TAG, "onLosing $network $maxMsToLive")
+        Log.d(logTag, "onLosing $network $maxMsToLive")
         history.add(Losing(network, maxMsToLive))
     }
 
     override fun onLost(network: Network) {
-        Log.d(TAG, "onLost $network")
+        Log.d(logTag, "onLost $network")
         history.add(Lost(network))
     }
 
     override fun onUnavailable() {
-        Log.d(TAG, "onUnavailable")
+        Log.d(logTag, "onUnavailable")
         history.add(Unavailable())
     }
 }
@@ -188,10 +193,11 @@
  */
 open class TestableNetworkCallback private constructor(
     src: TestableNetworkCallback?,
-    val defaultTimeoutMs: Long = DEFAULT_TIMEOUT,
-    val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
-    val waiterFunc: Runnable = NOOP // "() -> Unit" would forbid calling with a void func from Java
-) : RecorderCallback(src) {
+    val defaultTimeoutMs: Long,
+    val defaultNoCallbackTimeoutMs: Long,
+    val waiterFunc: Runnable,
+    logTag: String
+) : RecorderCallback(src, logTag) {
     /**
      * Construct a testable network callback.
      * @param timeoutMs the default timeout for expecting a callback. Default 30 seconds. This
@@ -213,14 +219,16 @@
     constructor(
         timeoutMs: Long = DEFAULT_TIMEOUT,
         noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
-        waiterFunc: Runnable = NOOP
-    ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc)
+        waiterFunc: Runnable = NOOP, // "() -> Unit" would forbid calling with a void func from Java
+        logTag: String = DEFAULT_TAG
+    ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc, logTag)
 
     fun createLinkedCopy() = TestableNetworkCallback(
         this,
         defaultTimeoutMs,
         defaultNoCallbackTimeoutMs,
-        waiterFunc
+        waiterFunc,
+        logTag
     )
 
     // The last available network, or null if any network was lost since the last call to
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 331e970..f71464c 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -14,13 +14,23 @@
 
 import re
 from mobly.controllers import android_device
-from net_tests_utils.host.python import adb_utils
+from mobly.controllers.android_device_lib.adb import AdbError
+from net_tests_utils.host.python import adb_utils, assert_utils
+
+
+# Constants.
+ETHER_BROADCAST = "FFFFFFFFFFFF"
+ETH_P_ETHERCAT = "88A4"
 
 
 class PatternNotFoundException(Exception):
   """Raised when the given pattern cannot be found."""
 
 
+class UnsupportedOperationException(Exception):
+  pass
+
+
 def get_apf_counter(
     ad: android_device.AndroidDevice, iface: str, counter_name: str
 ) -> int:
@@ -75,3 +85,108 @@
 
   ad.log.debug("Getting apf counters: " + str(result))
   return result
+
+
+def get_hardware_address(
+    ad: android_device.AndroidDevice, iface_name: str
+) -> str:
+  """Retrieves the hardware (MAC) address for a given network interface.
+
+  Returns:
+      The hex representative of the MAC address in uppercase.
+      E.g. 12:34:56:78:90:AB
+
+  Raises:
+      PatternNotFoundException: If the MAC address is not found in the command
+      output.
+  """
+
+  # Run the "ip link" command and get its output.
+  ip_link_output = adb_utils.adb_shell(ad, f"ip link show {iface_name}")
+
+  # Regular expression to extract the MAC address.
+  # Parse hardware address from ip link output like below:
+  # 46: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq ...
+  #    link/ether 72:05:77:82:21:e0 brd ff:ff:ff:ff:ff:ff
+  pattern = r"link/ether (([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})"
+  match = re.search(pattern, ip_link_output)
+
+  if match:
+    return match.group(1).upper()  # Extract the MAC address string.
+  else:
+    raise PatternNotFoundException(
+        "Cannot get hardware address for " + iface_name
+    )
+
+
+def send_broadcast_empty_ethercat_packet(
+    ad: android_device.AndroidDevice, iface_name: str
+) -> None:
+  """Transmits a broadcast empty EtherCat packet on the specified interface."""
+
+  # Get the interface's MAC address.
+  mac_address = get_hardware_address(ad, iface_name)
+
+  # TODO: Build packet by using scapy library.
+  # Ethernet header (14 bytes).
+  packet = ETHER_BROADCAST  # Destination MAC (broadcast)
+  packet += mac_address.replace(":", "")  # Source MAC
+  packet += ETH_P_ETHERCAT  # EtherType (EtherCAT)
+
+  # EtherCAT header (2 bytes) + 44 bytes of zero padding.
+  packet += "00" * 46
+
+  # Send the packet using a raw socket.
+  send_raw_packet_downstream(ad, iface_name, packet)
+
+
+def send_raw_packet_downstream(
+    ad: android_device.AndroidDevice,
+    iface_name: str,
+    packet_in_hex: str,
+) -> None:
+  """Sends a raw packet over the specified downstream interface.
+
+  This function constructs and sends a raw packet using the
+  `send-raw-packet-downstream`
+  command provided by NetworkStack process. It's primarily intended for testing
+  purposes.
+
+  Args:
+      ad: The AndroidDevice object representing the connected device.
+      iface_name: The name of the network interface to use (e.g., "wlan0",
+        "eth0").
+      packet_in_hex: The raw packet data starting from L2 header encoded in
+        hexadecimal string format.
+
+  Raises:
+      UnsupportedOperationException: If the NetworkStack doesn't support
+        the `send-raw-packet` command.
+      UnexpectedBehaviorException: If the command execution produces unexpected
+        output other than an empty response or "Unknown command".
+
+  Important Considerations:
+      Security: This method only works on tethering downstream interfaces due
+        to security restrictions.
+      Packet Format: The `packet_in_hex` must be a valid hexadecimal
+        representation of a packet starting from L2 header.
+  """
+
+  cmd = (
+      "cmd network_stack send-raw-packet-downstream"
+      f" {iface_name} {packet_in_hex}"
+  )
+
+  # Expect no output or Unknown command if NetworkStack is too old. Throw otherwise.
+  try:
+    output = adb_utils.adb_shell(ad, cmd)
+  except AdbError as e:
+    output = str(e.stdout)
+  if output:
+    if "Unknown command" in output:
+      raise UnsupportedOperationException(
+          "send-raw-packet-downstream command is not supported."
+      )
+    raise assert_utils.UnexpectedBehaviorError(
+        f"Got unexpected output: {output} for command: {cmd}."
+    )
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index 840831b..0cfc361 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,14 +1,17 @@
 # Lint as: python3
 """Connectivity multi devices tests."""
 import sys
+
+from mobly import asserts
 from mobly import base_test
 from mobly import test_runner
 from mobly import utils
 from mobly.controllers import android_device
-from net_tests_utils.host.python import mdns_utils, tether_utils
+from net_tests_utils.host.python import adb_utils, apf_utils, assert_utils, mdns_utils, tether_utils
 from net_tests_utils.host.python.tether_utils import UpstreamType
 
 CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED = "DROPPED_ETHERTYPE_NOT_ALLOWED"
 
 
 class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
@@ -68,19 +71,61 @@
     try:
       # Connectivity of the client verified by asserting the validated capability.
       tether_utils.setup_hotspot_and_client_for_upstream_type(
-        self.serverDevice, self.clientDevice, UpstreamType.NONE
+          self.serverDevice, self.clientDevice, UpstreamType.NONE
       )
       mdns_utils.register_mdns_service_and_discover_resolve(
-        self.clientDevice, self.serverDevice
+          self.clientDevice, self.serverDevice
       )
     finally:
-      mdns_utils.cleanup_mdns_service(
-        self.clientDevice, self.serverDevice
-      )
+      mdns_utils.cleanup_mdns_service(self.clientDevice, self.serverDevice)
       tether_utils.cleanup_tethering_for_upstream_type(
-        self.serverDevice, UpstreamType.NONE
+          self.serverDevice, UpstreamType.NONE
       )
 
+  def test_apf_drop_ethercat(self):
+    tether_utils.assume_hotspot_test_preconditions(
+        self.serverDevice, self.clientDevice, UpstreamType.NONE
+    )
+    client = self.clientDevice.connectivity_multi_devices_snippet
+    try:
+      server_iface_name, client_network = (
+          tether_utils.setup_hotspot_and_client_for_upstream_type(
+              self.serverDevice, self.clientDevice, UpstreamType.NONE
+          )
+      )
+      client_iface_name = client.getInterfaceNameFromNetworkHandle(client_network)
+
+      adb_utils.set_doze_mode(self.clientDevice, True)
+
+      count_before_test = apf_utils.get_apf_counter(
+          self.clientDevice,
+          client_iface_name,
+          COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
+      )
+      try:
+        apf_utils.send_broadcast_empty_ethercat_packet(
+            self.serverDevice, server_iface_name
+        )
+      except apf_utils.UnsupportedOperationException:
+        asserts.skip(
+            "NetworkStack is too old to support send raw packet, skip test."
+        )
+
+      assert_utils.expect_with_retry(
+          lambda: apf_utils.get_apf_counter(
+              self.clientDevice,
+              client_iface_name,
+              COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
+          )
+          > count_before_test
+      )
+    finally:
+      adb_utils.set_doze_mode(self.clientDevice, False)
+      tether_utils.cleanup_tethering_for_upstream_type(
+          self.serverDevice, UpstreamType.NONE
+      )
+
+
 if __name__ == "__main__":
   # Take test args
   if "--" in sys.argv:
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index f4ad2c4..9bdf4a3 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -21,6 +21,7 @@
 import android.content.pm.PackageManager.FEATURE_TELEPHONY
 import android.content.pm.PackageManager.FEATURE_WIFI
 import android.net.ConnectivityManager
+import android.net.Network
 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkRequest
@@ -129,6 +130,12 @@
         }
     }
 
+    @Rpc(description = "Get interface name from NetworkHandle")
+    fun getInterfaceNameFromNetworkHandle(networkHandle: Long): String {
+        val network = Network.fromNetworkHandle(networkHandle)
+        return cm.getLinkProperties(network)!!.getInterfaceName()!!
+    }
+
     @Rpc(description = "Check whether the device supports hotspot feature.")
     fun hasHotspotFeature(): Boolean {
         val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
@@ -140,7 +147,7 @@
     }
 
     @Rpc(description = "Start a hotspot with given SSID and passphrase.")
-    fun startHotspot(ssid: String, passphrase: String) {
+    fun startHotspot(ssid: String, passphrase: String): String {
         // Store old config.
         runAsShell(OVERRIDE_WIFI_CONFIG) {
             oldSoftApConfig = wifiManager.getSoftApConfiguration()
@@ -157,7 +164,7 @@
         val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
         try {
             tetheringCallback.expectNoTetheringActive()
-            ctsTetheringUtils.startWifiTethering(tetheringCallback)
+            return ctsTetheringUtils.startWifiTethering(tetheringCallback).getInterface()
         } finally {
             ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback)
         }
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 284fcae..f45f881 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -59,7 +59,6 @@
     @After
     override fun tearDown() {
         super.tearDown()
-        setIncludeTestInterfaces(false)
     }
 
     @Test
@@ -107,7 +106,6 @@
     @Test
     fun testMdnsDiscoveryWorkOnTetheringInterface() {
         assumeFalse(isInterfaceForTetheringAvailable())
-        setIncludeTestInterfaces(true)
 
         var downstreamIface: TestNetworkInterface? = null
         var tetheringEventCallback: MyTetheringEventCallback? = null
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
index cf990b1..a7083dc 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
@@ -17,8 +17,11 @@
 package com.android.server.connectivityservice
 
 import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_AVAILABLE
+import android.net.ConnectivityManager.CALLBACK_BLK_CHANGED
 import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
 import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED
 import android.net.ConnectivityManager.CALLBACK_LOST
 import android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL
 import android.net.LinkAddress
@@ -35,7 +38,9 @@
 import com.android.testutils.RecorderCallback.CallbackEntry
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.tryTest
+import java.lang.reflect.Modifier
 import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -129,6 +134,32 @@
         listenCb.expect<CallbackEntry.CapabilitiesChanged>()
         listenCb.assertNoCallback(timeoutMs = 0L)
     }
+
+    @Test
+    fun testDeclaredMethodsFlagsToString() {
+        assertEquals("NONE", ConnectivityService.declaredMethodsFlagsToString(0))
+        assertEquals("ALL", ConnectivityService.declaredMethodsFlagsToString(0.inv()))
+        assertEquals("AVAIL|NC|LP|BLK|LOCALINF", ConnectivityService.declaredMethodsFlagsToString(
+            (1 shl CALLBACK_AVAILABLE) or
+            (1 shl CALLBACK_CAP_CHANGED) or
+            (1 shl CALLBACK_IP_CHANGED) or
+            (1 shl CALLBACK_BLK_CHANGED) or
+            (1 shl CALLBACK_LOCAL_NETWORK_INFO_CHANGED)
+        ))
+
+        // EXPIRE_LEGACY_REQUEST (=8) is only used in ConnectivityManager and not included.
+        // CALLBACK_TRANSITIVE_CALLS_ONLY (=0) is not a callback so not included either.
+        assertEquals(
+            "PRECHK|AVAIL|LOSING|LOST|UNAVAIL|NC|LP|SUSP|RESUME|BLK|LOCALINF|0x7fffe101",
+            ConnectivityService.declaredMethodsFlagsToString(0x7fff_ffff)
+        )
+        // The toString method and the assertion above need to be updated if constants are added
+        val constants = ConnectivityManager::class.java.declaredFields.filter {
+            Modifier.isStatic(it.modifiers) && Modifier.isFinal(it.modifiers) &&
+                    it.name.startsWith("CALLBACK_")
+        }
+        assertEquals(12, constants.size)
+    }
 }
 
 private fun AtomicInteger.withFlags(vararg flags: Int, action: () -> Unit) {