Merge "Wrap some IpServer commands"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index b1bee32..829e66a 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -75,7 +75,6 @@
     libs: [
         "framework-tethering.impl",
     ],
-    plugins: ["java_api_finder"],
     manifest: "AndroidManifestBase.xml",
     lint: { strict_updatability_linting: true },
 }
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index c2c9fc4..da69a8d 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -67,16 +67,13 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import android.app.UiAutomation;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.net.EthernetManager.TetheredInterfaceCallback;
 import android.net.EthernetManager.TetheredInterfaceRequest;
 import android.net.TetheringManager.StartTetheringCallback;
 import android.net.TetheringManager.TetheringEventCallback;
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringTester.TetheredDevice;
-import android.net.cts.util.CtsNetUtils;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -88,7 +85,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -143,17 +139,12 @@
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
-public class EthernetTetheringTest {
+public class EthernetTetheringTest extends EthernetTetheringTestBase {
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
 
     private static final String TAG = EthernetTetheringTest.class.getSimpleName();
-    private static final int TIMEOUT_MS = 5000;
-    // Used to check if any tethering interface is available. Choose 200ms to be request timeout
-    // because the average interface requested time on cuttlefish@acloud is around 10ms.
-    // See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
-    private static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
-    private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
+
     private static final int DUMP_POLLING_MAX_RETRY = 100;
     private static final int DUMP_POLLING_INTERVAL_MS = 50;
     // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
@@ -168,35 +159,14 @@
     // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
     private static final int TX_UDP_PACKET_SIZE = 44;
     private static final int TX_UDP_PACKET_COUNT = 123;
-    private static final long WAIT_RA_TIMEOUT_MS = 2000;
-
-    private static final MacAddress TEST_MAC = MacAddress.fromString("1:2:3:4:5:6");
-    private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/24");
-    private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
-    private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
-    private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
-    private static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96");
-    private static final Inet6Address REMOTE_NAT64_ADDR =
-            (Inet6Address) parseNumericAddress("64:ff9b::808:808");
-    private static final Inet6Address REMOTE_IP6_ADDR =
-            (Inet6Address) parseNumericAddress("2002:db8:1::515:ca");
-    private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
-            ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
-    private static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]);
 
     private static final short DNS_PORT = 53;
-    private static final short WINDOW = (short) 0x2000;
-    private static final short URGENT_POINTER = 0;
 
     private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
     private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
     private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
     private static final String LINE_DELIMITER = "\\n";
 
-    // version=6, traffic class=0x0, flowlabel=0x0;
-    private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
-    private static final short HOP_LIMIT = 0x40;
-
     private static final short ICMPECHO_CODE = 0x0;
     private static final short ICMPECHO_ID = 0x0;
     private static final short ICMPECHO_SEQ = 0x0;
@@ -261,27 +231,9 @@
             (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04  /* Address: 1.2.3.4 */
     };
 
-    private final Context mContext = InstrumentationRegistry.getContext();
-    private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
-    private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
-    private final PackageManager mPackageManager = mContext.getPackageManager();
-    private final CtsNetUtils mCtsNetUtils = new CtsNetUtils(mContext);
-
-    private TestNetworkInterface mDownstreamIface;
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-    private TapPacketReader mDownstreamReader;
-    private TapPacketReader mUpstreamReader;
-
     private TetheredInterfaceRequester mTetheredInterfaceRequester;
     private MyTetheringEventCallback mTetheringEventCallback;
 
-    private UiAutomation mUiAutomation =
-            InstrumentationRegistry.getInstrumentation().getUiAutomation();
-    private boolean mRunTests;
-
-    private TestNetworkTracker mUpstreamTracker;
-
     @Before
     public void setUp() throws Exception {
         mHandlerThread = new HandlerThread(getClass().getSimpleName());
@@ -295,6 +247,30 @@
         mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
     }
 
+    private void maybeStopTapPacketReader(final TapPacketReader tapPacketReader)
+            throws Exception {
+        if (tapPacketReader != null) {
+            TapPacketReader reader = tapPacketReader;
+            mHandler.post(() -> reader.stop());
+        }
+    }
+
+    private void maybeCloseTestInterface(final TestNetworkInterface testInterface)
+            throws Exception {
+        if (testInterface != null) {
+            testInterface.getFileDescriptor().close();
+            Log.d(TAG, "Deleted test interface " + testInterface.getInterfaceName());
+        }
+    }
+
+    private void maybeUnregisterTetheringEventCallback(final MyTetheringEventCallback callback)
+            throws Exception {
+        if (callback != null) {
+            callback.awaitInterfaceUntethered();
+            callback.unregister();
+        }
+    }
+
     private void cleanUp() throws Exception {
         setPreferTestNetworks(false);
 
@@ -310,24 +286,19 @@
             mUpstreamReader = null;
         }
 
-        if (mDownstreamReader != null) {
-            TapPacketReader reader = mDownstreamReader;
-            mHandler.post(() -> reader.stop());
-            mDownstreamReader = null;
-        }
-
+        maybeStopTapPacketReader(mDownstreamReader);
+        mDownstreamReader = null;
         // To avoid flaky which caused by the next test started but the previous interface is not
         // untracked from EthernetTracker yet. Just delete the test interface without explicitly
         // calling TetheringManager#stopTethering could let EthernetTracker untrack the test
         // interface from server mode before tethering stopped. Thus, awaitInterfaceUntethered
         // could not only make sure tethering is stopped but also guarantee the test interface is
         // untracked from EthernetTracker.
-        maybeDeleteTestInterface();
-        if (mTetheringEventCallback != null) {
-            mTetheringEventCallback.awaitInterfaceUntethered();
-            mTetheringEventCallback.unregister();
-            mTetheringEventCallback = null;
-        }
+        maybeCloseTestInterface(mDownstreamIface);
+        mDownstreamIface = null;
+        maybeUnregisterTetheringEventCallback(mTetheringEventCallback);
+        mTetheringEventCallback = null;
+
         runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
         setIncludeTestInterfaces(false);
     }
@@ -383,26 +354,50 @@
         });
     }
 
+    private String getTetheredInterface() throws Exception {
+        return mTetheredInterfaceRequester.getInterface();
+    }
+
+    private CompletableFuture<String> requestTetheredInterface() throws Exception {
+        return mTetheredInterfaceRequester.requestInterface();
+    }
+
     @Test
     public void testVirtualEthernetAlreadyExists() throws Exception {
         // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
         assumeFalse(isInterfaceForTetheringAvailable());
 
-        mDownstreamIface = createTestInterface();
-        // This must be done now because as soon as setIncludeTestInterfaces(true) is called, the
-        // interface will be placed in client mode, which will delete the link-local address.
-        // At that point NetworkInterface.getByName() will cease to work on the interface, because
-        // starting in R NetworkInterface can no longer see interfaces without IP addresses.
-        int mtu = getMTU(mDownstreamIface);
+        TestNetworkInterface downstreamIface = null;
+        MyTetheringEventCallback tetheringEventCallback = null;
+        TapPacketReader downstreamReader = null;
 
-        Log.d(TAG, "Including test interfaces");
-        setIncludeTestInterfaces(true);
+        try {
+            downstreamIface = createTestInterface();
+            // This must be done now because as soon as setIncludeTestInterfaces(true) is called,
+            // the interface will be placed in client mode, which will delete the link-local
+            // address. At that point NetworkInterface.getByName() will cease to work on the
+            // interface, because starting in R NetworkInterface can no longer see interfaces
+            // without IP addresses.
+            int mtu = getMTU(downstreamIface);
 
-        final String iface = mTetheredInterfaceRequester.getInterface();
-        assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mDownstreamIface.getInterfaceName(), iface);
+            Log.d(TAG, "Including test interfaces");
+            setIncludeTestInterfaces(true);
 
-        checkVirtualEthernet(mDownstreamIface, mtu);
+            final String iface = getTetheredInterface();
+            assertEquals("TetheredInterfaceCallback for unexpected interface",
+                    downstreamIface.getInterfaceName(), iface);
+
+            // Check virtual ethernet.
+            FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
+            downstreamReader = makePacketReader(fd, mtu);
+            tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
+                    null /* any upstream */);
+            checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
+        } finally {
+            maybeStopTapPacketReader(downstreamReader);
+            maybeCloseTestInterface(downstreamIface);
+            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+        }
     }
 
     @Test
@@ -410,17 +405,32 @@
         // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
         assumeFalse(isInterfaceForTetheringAvailable());
 
-        CompletableFuture<String> futureIface = mTetheredInterfaceRequester.requestInterface();
+        CompletableFuture<String> futureIface = requestTetheredInterface();
 
         setIncludeTestInterfaces(true);
 
-        mDownstreamIface = createTestInterface();
+        TestNetworkInterface downstreamIface = null;
+        MyTetheringEventCallback tetheringEventCallback = null;
+        TapPacketReader downstreamReader = null;
 
-        final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mDownstreamIface.getInterfaceName(), iface);
+        try {
+            downstreamIface = createTestInterface();
 
-        checkVirtualEthernet(mDownstreamIface, getMTU(mDownstreamIface));
+            final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            assertEquals("TetheredInterfaceCallback for unexpected interface",
+                    downstreamIface.getInterfaceName(), iface);
+
+            // Check virtual ethernet.
+            FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
+            downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
+            tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
+                    null /* any upstream */);
+            checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
+        } finally {
+            maybeStopTapPacketReader(downstreamReader);
+            maybeCloseTestInterface(downstreamIface);
+            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+        }
     }
 
     @Test
@@ -429,42 +439,51 @@
 
         setIncludeTestInterfaces(true);
 
-        mDownstreamIface = createTestInterface();
-
-        final String iface = mTetheredInterfaceRequester.getInterface();
-        assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mDownstreamIface.getInterfaceName(), iface);
-
-        assertInvalidStaticIpv4Request(iface, null, null);
-        assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
-        assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
-        assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
-        assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
-        assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
-        assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");
-
-        final String localAddr = "192.0.2.3/28";
-        final String clientAddr = "192.0.2.2/28";
-        mTetheringEventCallback = enableEthernetTethering(iface,
-                requestWithStaticIpv4(localAddr, clientAddr), null /* any upstream */);
-
-        mTetheringEventCallback.awaitInterfaceTethered();
-        assertInterfaceHasIpAddress(iface, localAddr);
-
-        byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
-        byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
-
-        FileDescriptor fd = mDownstreamIface.getFileDescriptor().getFileDescriptor();
-        mDownstreamReader = makePacketReader(fd, getMTU(mDownstreamIface));
-        TetheringTester tester = new TetheringTester(mDownstreamReader);
-        DhcpResults dhcpResults = tester.runDhcp(client1);
-        assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
+        TestNetworkInterface downstreamIface = null;
+        MyTetheringEventCallback tetheringEventCallback = null;
+        TapPacketReader downstreamReader = null;
 
         try {
-            tester.runDhcp(client2);
-            fail("Only one client should get an IP address");
-        } catch (TimeoutException expected) { }
+            downstreamIface = createTestInterface();
 
+            final String iface = getTetheredInterface();
+            assertEquals("TetheredInterfaceCallback for unexpected interface",
+                    downstreamIface.getInterfaceName(), iface);
+
+            assertInvalidStaticIpv4Request(iface, null, null);
+            assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
+            assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
+            assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
+            assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
+            assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
+            assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");
+
+            final String localAddr = "192.0.2.3/28";
+            final String clientAddr = "192.0.2.2/28";
+            tetheringEventCallback = enableEthernetTethering(iface,
+                    requestWithStaticIpv4(localAddr, clientAddr), null /* any upstream */);
+
+            tetheringEventCallback.awaitInterfaceTethered();
+            assertInterfaceHasIpAddress(iface, localAddr);
+
+            byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
+            byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
+
+            FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
+            downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
+            TetheringTester tester = new TetheringTester(downstreamReader);
+            DhcpResults dhcpResults = tester.runDhcp(client1);
+            assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
+
+            try {
+                tester.runDhcp(client2);
+                fail("Only one client should get an IP address");
+            } catch (TimeoutException expected) { }
+        } finally {
+            maybeStopTapPacketReader(downstreamReader);
+            maybeCloseTestInterface(downstreamIface);
+            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+        }
     }
 
     private static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
@@ -510,26 +529,36 @@
 
         setIncludeTestInterfaces(true);
 
-        mDownstreamIface = createTestInterface();
+        TestNetworkInterface downstreamIface = null;
+        MyTetheringEventCallback tetheringEventCallback = null;
+        TapPacketReader downstreamReader = null;
 
-        final String iface = mTetheredInterfaceRequester.getInterface();
-        assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mDownstreamIface.getInterfaceName(), iface);
+        try {
+            downstreamIface = createTestInterface();
 
-        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
-                .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
-        mTetheringEventCallback = enableEthernetTethering(iface, request,
-                null /* any upstream */);
-        mTetheringEventCallback.awaitInterfaceLocalOnly();
+            final String iface = getTetheredInterface();
+            assertEquals("TetheredInterfaceCallback for unexpected interface",
+                    downstreamIface.getInterfaceName(), iface);
 
-        // makePacketReader only works after tethering is started, because until then the interface
-        // does not have an IP address, and unprivileged apps cannot see interfaces without IP
-        // addresses. This shouldn't be flaky because the TAP interface will buffer all packets even
-        // before the reader is started.
-        mDownstreamReader = makePacketReader(mDownstreamIface);
+            final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
+                    .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
+            tetheringEventCallback = enableEthernetTethering(iface, request,
+                    null /* any upstream */);
+            tetheringEventCallback.awaitInterfaceLocalOnly();
 
-        waitForRouterAdvertisement(mDownstreamReader, iface, WAIT_RA_TIMEOUT_MS);
-        expectLocalOnlyAddresses(iface);
+            // makePacketReader only works after tethering is started, because until then the
+            // interface does not have an IP address, and unprivileged apps cannot see interfaces
+            // without IP addresses. This shouldn't be flaky because the TAP interface will buffer
+            // all packets even before the reader is started.
+            downstreamReader = makePacketReader(downstreamIface);
+
+            waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS);
+            expectLocalOnlyAddresses(iface);
+        } finally {
+            maybeStopTapPacketReader(downstreamReader);
+            maybeCloseTestInterface(downstreamIface);
+            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+        }
     }
 
     private boolean isAdbOverNetwork() {
@@ -546,12 +575,16 @@
         // from client mode to server mode. See b/160389275.
         assumeFalse(isAdbOverNetwork());
 
-        // Get an interface to use.
-        final String iface = mTetheredInterfaceRequester.getInterface();
+        MyTetheringEventCallback tetheringEventCallback = null;
+        try {
+            // Get an interface to use.
+            final String iface = getTetheredInterface();
 
-        // Enable Ethernet tethering and check that it starts.
-        mTetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
-
+            // Enable Ethernet tethering and check that it starts.
+            tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
+        } finally {
+            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+        }
         // There is nothing more we can do on a physical interface without connecting an actual
         // client, which is not possible in this test.
     }
@@ -810,15 +843,8 @@
         return reader;
     }
 
-    private void checkVirtualEthernet(TestNetworkInterface iface, int mtu) throws Exception {
-        FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
-        mDownstreamReader = makePacketReader(fd, mtu);
-        mTetheringEventCallback = enableEthernetTethering(iface.getInterfaceName(),
-                null /* any upstream */);
-        checkTetheredClientCallbacks(mDownstreamReader);
-    }
-
-    private void checkTetheredClientCallbacks(TapPacketReader packetReader) throws Exception {
+    private void checkTetheredClientCallbacks(final TapPacketReader packetReader,
+            final MyTetheringEventCallback tetheringEventCallback) throws Exception {
         // Create a fake client.
         byte[] clientMacAddr = new byte[6];
         new Random().nextBytes(clientMacAddr);
@@ -826,7 +852,7 @@
         TetheringTester tester = new TetheringTester(packetReader);
         DhcpResults dhcpResults = tester.runDhcp(clientMacAddr);
 
-        final Collection<TetheredClient> clients = mTetheringEventCallback.awaitClientConnected();
+        final Collection<TetheredClient> clients = tetheringEventCallback.awaitClientConnected();
         assertEquals(1, clients.size());
         final TetheredClient client = clients.iterator().next();
 
@@ -945,14 +971,6 @@
         return iface;
     }
 
-    private void maybeDeleteTestInterface() throws Exception {
-        if (mDownstreamIface != null) {
-            mDownstreamIface.getFileDescriptor().close();
-            Log.d(TAG, "Deleted test interface " + mDownstreamIface.getInterfaceName());
-            mDownstreamIface = null;
-        }
-    }
-
     private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
             final List<InetAddress> dnses) throws Exception {
         setPreferTestNetworks(true);
@@ -1002,22 +1020,6 @@
     // remote ip              public ip                           private ip
     // 8.8.8.8:443            <Upstream ip>:9876                  <TetheredDevice ip>:9876
     //
-    private static final Inet4Address REMOTE_IP4_ADDR =
-            (Inet4Address) parseNumericAddress("8.8.8.8");
-    // Used by public port and private port. Assume port 9876 has not been used yet before the
-    // testing that public port and private port are the same in the testing. Note that NAT port
-    // forwarding could be different between private port and public port.
-    // TODO: move to the start of test class.
-    private static final short LOCAL_PORT = 9876;
-    private static final short REMOTE_PORT = 433;
-    private static final byte TYPE_OF_SERVICE = 0;
-    private static final short ID = 27149;
-    private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
-    private static final byte TIME_TO_LIVE = (byte) 0x40;
-    private static final ByteBuffer RX_PAYLOAD =
-            ByteBuffer.wrap(new byte[] { (byte) 0x12, (byte) 0x34 });
-    private static final ByteBuffer TX_PAYLOAD =
-            ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 });
 
     private short getEthType(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
         return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6;
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java
new file mode 100644
index 0000000..d58a60c
--- /dev/null
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.InetAddresses.parseNumericAddress;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.cts.util.CtsNetUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.testutils.TapPacketReader;
+import com.android.testutils.TestNetworkTracker;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * TODO: Common variables or methods shared between CtsEthernetTetheringTest and
+ * MtsEthernetTetheringTest.
+ */
+public abstract class EthernetTetheringTestBase {
+    private static final String TAG = EthernetTetheringTestBase.class.getSimpleName();
+
+    protected static final int TIMEOUT_MS = 5000;
+    // Used to check if any tethering interface is available. Choose 200ms to be request timeout
+    // because the average interface requested time on cuttlefish@acloud is around 10ms.
+    // See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
+    protected static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
+    protected static final int TETHER_REACHABILITY_ATTEMPTS = 20;
+    protected static final long WAIT_RA_TIMEOUT_MS = 2000;
+
+    // Address and NAT prefix definition.
+    protected static final MacAddress TEST_MAC = MacAddress.fromString("1:2:3:4:5:6");
+    protected static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/24");
+    protected static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
+    protected static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
+    protected static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
+
+    protected static final Inet4Address REMOTE_IP4_ADDR =
+            (Inet4Address) parseNumericAddress("8.8.8.8");
+    protected static final Inet6Address REMOTE_IP6_ADDR =
+            (Inet6Address) parseNumericAddress("2002:db8:1::515:ca");
+    protected static final Inet6Address REMOTE_NAT64_ADDR =
+            (Inet6Address) parseNumericAddress("64:ff9b::808:808");
+    protected static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96");
+
+    // IPv4 header definition.
+    protected static final short ID = 27149;
+    protected static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
+    protected static final byte TIME_TO_LIVE = (byte) 0x40;
+    protected static final byte TYPE_OF_SERVICE = 0;
+
+    // IPv6 header definition.
+    protected static final short HOP_LIMIT = 0x40;
+    // version=6, traffic class=0x0, flowlabel=0x0;
+    protected static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
+
+    // UDP and TCP header definition.
+    // LOCAL_PORT is used by public port and private port. Assume port 9876 has not been used yet
+    // before the testing that public port and private port are the same in the testing. Note that
+    // NAT port forwarding could be different between private port and public port.
+    protected static final short LOCAL_PORT = 9876;
+    protected static final short REMOTE_PORT = 433;
+    protected static final short WINDOW = (short) 0x2000;
+    protected static final short URGENT_POINTER = 0;
+
+    // Payload definition.
+    protected static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]);
+    protected static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
+            ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
+    protected static final ByteBuffer RX_PAYLOAD =
+            ByteBuffer.wrap(new byte[] { (byte) 0x12, (byte) 0x34 });
+    protected static final ByteBuffer TX_PAYLOAD =
+            ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 });
+
+    protected final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    protected final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
+    protected final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
+    protected final PackageManager mPackageManager = mContext.getPackageManager();
+    protected final CtsNetUtils mCtsNetUtils = new CtsNetUtils(mContext);
+    protected final UiAutomation mUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+    // Late initialization in setUp()
+    protected boolean mRunTests;
+    protected HandlerThread mHandlerThread;
+    protected Handler mHandler;
+
+    // Late initialization in initTetheringTester().
+    protected TapPacketReader mUpstreamReader;
+    protected TestNetworkTracker mUpstreamTracker;
+    protected TestNetworkInterface mDownstreamIface;
+    protected TapPacketReader mDownstreamReader;
+}
diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java
index 48e5092..edfd21c 100644
--- a/framework-t/src/android/net/NetworkIdentity.java
+++ b/framework-t/src/android/net/NetworkIdentity.java
@@ -400,10 +400,8 @@
             setSubscriberId(snapshot.getSubscriberId());
             setRoaming(!snapshot.getNetworkCapabilities().hasCapability(
                     NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING));
-            setMetered(!(snapshot.getNetworkCapabilities().hasCapability(
-                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                    || snapshot.getNetworkCapabilities().hasCapability(
-                    NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)));
+            setMetered(!snapshot.getNetworkCapabilities().hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
 
             setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities()));
 
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
index c84caa6..2d0d327 100644
--- a/nearby/halfsheet/Android.bp
+++ b/nearby/halfsheet/Android.bp
@@ -43,7 +43,6 @@
         "com.google.android.material_material",
         "fast-pair-lite-protos",
     ],
-    plugins: ["java_api_finder"],
     manifest: "AndroidManifest.xml",
     jarjar_rules: ":nearby-jarjar-rules",
     apex_available: ["com.android.tethering",],
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 9b23395..316f9e5 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -75,7 +75,6 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
@@ -8062,10 +8061,6 @@
         final boolean oldMetered = prevNc.isMetered();
         final boolean newMetered = newNc.isMetered();
         final boolean meteredChanged = oldMetered != newMetered;
-        final boolean oldTempMetered = prevNc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        final boolean newTempMetered = newNc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        final boolean tempMeteredChanged = oldTempMetered != newTempMetered;
-
 
         if (meteredChanged) {
             maybeNotifyNetworkBlocked(nai, oldMetered, newMetered,
@@ -8076,7 +8071,7 @@
                 != newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
 
         // Report changes that are interesting for network statistics tracking.
-        if (meteredChanged || roamingChanged || tempMeteredChanged) {
+        if (meteredChanged || roamingChanged) {
             notifyIfacesChangedForNetworkStats();
         }
 
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index d84328c..9667f8f 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -101,15 +101,15 @@
                 false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
         assertFalse(netIdent2.isMetered())
 
-        // Verify network is not metered because it has NET_CAPABILITY_TEMPORARILY_NOT_METERED
-        // capability .
+        // In current design, a network that has NET_CAPABILITY_TEMPORARILY_NOT_METERED
+        // capability will be treated as metered.
         val capsTempNotMetered = NetworkCapabilities().apply {
             setCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED, true)
         }
         val netIdent3 = NetworkIdentity.buildNetworkIdentity(mockContext,
                 buildMobileNetworkStateSnapshot(capsTempNotMetered, TEST_IMSI1),
                 false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
-        assertFalse(netIdent3.isMetered())
+        assertTrue(netIdent3.isMetered())
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index f80b9bd..67cc7bd 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -7336,10 +7336,11 @@
         expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
 
-        // Temp metered change should update ifaces
+        // Temp metered change shouldn't update ifaces
         mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
         waitForIdle();
-        expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
+        verify(mStatsManager, never()).notifyNetworkStatus(eq(onlyCell()),
+                any(List.class), eq(MOBILE_IFNAME), any(List.class));
         reset(mStatsManager);
 
         // Congested change shouldn't update ifaces
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index e8f30d6..d7c90d8 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -995,8 +995,8 @@
         forcePollAndWaitForIdle();
 
         // Verify service recorded history.
-        assertUidTotal(templateMetered5g, UID_RED, 128L, 2L, 128L, 2L, 0);
-        assertUidTotal(templateNonMetered5g, UID_RED, 256, 3L, 128L, 5L, 0);
+        assertUidTotal(templateMetered5g, UID_RED, 384L, 5L, 256L, 7L, 0);
+        assertUidTotal(templateNonMetered5g, UID_RED, 0L, 0L, 0L, 0L, 0);
     }
 
     @Test
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index 990bbbe..e6f3978 100644
--- a/tools/gn2bp/Android.bp.swp
+++ b/tools/gn2bp/Android.bp.swp
@@ -2365,13 +2365,13 @@
     cmd: "$(location build/android/gyp/aidl.py) --aidl-path " +
          "../../third_party/android_sdk/public/build-tools/33.0.0/aidl " +
          "--imports " +
-         "["../../third_party/android_sdk/public/platforms/android-33/framework.aidl"] " +
+         "[\"../../third_party/android_sdk/public/platforms/android-33/framework.aidl\"] " +
          "--srcjar " +
          "gen/base/base_java_aidl.srcjar " +
          "--depfile " +
          "gen/base/base_java_aidl.d " +
          "--includes " +
-         "["../../base/android/java/src"] " +
+         "[\"../../base/android/java/src\"] " +
          "../../base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl " +
          "../../base/android/java/src/org/chromium/base/process_launcher/IParentProcess.aidl",
     out: [
@@ -5687,7 +5687,7 @@
 java_genrule {
     name: "cronet_aml_build_android_build_config_gen",
     cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
-         "["../../", "gen"] " +
+         "[\"../../\", \"gen\"] " +
          "--output " +
          "gen/build/android/build_config_gen.srcjar " +
          "--define " +
@@ -7373,7 +7373,7 @@
 
 // GN: //components/cronet/android:cronet_jni_registration
 java_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_registration_java",
+    name: "cronet_aml_components_cronet_android_cronet_jni_registration__java",
     srcs: [
         "base/android/java/src/org/chromium/base/ActivityState.java",
         "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
@@ -7674,9 +7674,6 @@
         "build/android/gyp/util/build_utils.py",
         "build/gn_helpers.py",
     ],
-    apex_available: [
-        "com.android.tethering",
-    ],
 }
 
 // GN: //components/cronet/android:cronet_static
@@ -7844,7 +7841,7 @@
          "-f " +
          "$(location build/util/LASTCHANGE) " +
          "-e " +
-         "API_LEVEL='20' " +
+         "API_LEVEL=20 " +
          "-o " +
          "$(out) " +
          "$(location components/cronet/android/java/src/org/chromium/net/impl/ImplVersion.template)",
@@ -7864,7 +7861,7 @@
 java_genrule {
     name: "cronet_aml_components_cronet_android_integrated_mode_state",
     cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
-         "["../../", "gen"] " +
+         "[\"../../\", \"gen\"] " +
          "--output " +
          "gen/components/cronet/android/integrated_mode_state.srcjar " +
          "../../components/cronet/android/java/src/org/chromium/net/impl/IntegratedModeState.template",
@@ -7885,7 +7882,7 @@
          "-f " +
          "$(location build/util/LASTCHANGE) " +
          "-e " +
-         "API_LEVEL='20' " +
+         "API_LEVEL=20 " +
          "-o " +
          "$(out) " +
          "$(location components/cronet/android/api/src/org/chromium/net/ApiVersion.template)",
@@ -7905,7 +7902,7 @@
 java_genrule {
     name: "cronet_aml_components_cronet_android_load_states_list",
     cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
-         "["../../", "gen"] " +
+         "[\"../../\", \"gen\"] " +
          "--output " +
          "gen/components/cronet/android/load_states_list.srcjar " +
          "../../components/cronet/android/java/src/org/chromium/net/impl/LoadState.template",
@@ -8264,7 +8261,7 @@
     cmd: "$(location build/util/version.py) -f " +
          "$(location chrome/VERSION) " +
          "-e " +
-         "VERSION_FULL='\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
+         "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
          "-o " +
          "$(out) " +
          "$(location components/cronet/version.h.in)",
@@ -8289,7 +8286,7 @@
     cmd: "$(location build/util/version.py) -f " +
          "$(location chrome/VERSION) " +
          "-e " +
-         "VERSION_FULL='\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
+         "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
          "-o " +
          "$(out) " +
          "$(location components/cronet/version.h.in)",
@@ -8314,7 +8311,7 @@
     cmd: "$(location build/util/version.py) -f " +
          "$(location chrome/VERSION) " +
          "-e " +
-         "VERSION_FULL='\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
+         "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
          "-o " +
          "$(out) " +
          "$(location components/cronet/version.h.in)",
@@ -8339,7 +8336,7 @@
     cmd: "$(location build/util/version.py) -f " +
          "$(location chrome/VERSION) " +
          "-e " +
-         "VERSION_FULL='\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
+         "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
          "-o " +
          "$(out) " +
          "$(location components/cronet/version.h.in)",
@@ -9778,7 +9775,7 @@
 java_genrule {
     name: "cronet_aml_net_android_net_errors_java",
     cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
-         "["../../", "gen"] " +
+         "[\"../../\", \"gen\"] " +
          "--output " +
          "gen/net/android/net_errors_java.srcjar " +
          "../../net/android/java/NetError.template",
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index ce71938..ef05126 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -108,6 +108,9 @@
 # Location of the protobuf src dir in the Android source tree.
 android_protobuf_src = 'external/protobuf/src'
 
+# put all args on a new line for better diffs.
+NEWLINE = ' " +\n         "'
+
 # Compiler flags which are passed through to the blueprint.
 cflag_allowlist = [
   # needed for zlib:zlib
@@ -620,17 +623,30 @@
   def __init__(self, target):
     # Just to be on the safe side, create a deep-copy.
     self.target = copy.deepcopy(target)
+    self.target.args = self._normalize_args()
+
+  def get_name(self):
+    return label_to_module_name(self.target.name)
+
+  def _normalize_args(self):
     # Convert ['--param=value'] to ['--param', 'value'] for consistency.
-    self.target.args = [str for it in self.target.args for str in it.split('=')]
+    # Escape quotations.
+    normalized_args = []
+    for arg in self.target.args:
+      arg = arg.replace('"', r'\"')
+      if arg.startswith('-'):
+        normalized_args.extend(arg.split('='))
+      else:
+        normalized_args.append(arg)
+    return normalized_args
 
   # There are three types of args:
   # - flags (--flag)
   # - value args (--arg value)
   # - list args (--arg value1 --arg value2)
+  # value args have exactly one arg value pair and list args have one or more arg value pairs.
   # Note that the set of list args contains the set of value args.
-  # value args must have exactly one arg value pair but list args could have one arg value pair.
-  # This is because list args with one arg value pair and value args can not be distinguished only
-  # from the desc.json
+  # This is because list and value args are identical when the list args has only one arg value pair
   # Some functions provide special implementations for each type, while others
   # work on all of them.
   def _has_arg(self, arg):
@@ -654,10 +670,7 @@
 
   # Whether an arg value pair appears exactly once
   def _is_value_arg(self, arg):
-    if operator.countOf(self.target.args, arg) != 1:
-      return False
-    i = self.target.args.index(arg)
-    return not self.target.args[i + 1].startswith('--')
+    return operator.countOf(self.target.args, arg) == 1 and self._is_list_arg(arg)
 
   def _get_value_arg(self, arg):
     assert(self._is_value_arg(arg))
@@ -720,19 +733,50 @@
   def _update_all_args(self, func):
     self.target.args = [func(arg) for arg in self.target.args]
 
-  def get_args(self):
-    return self.target.args
+  def get_cmd(self):
+    arg_string = NEWLINE.join(self.target.args)
+    cmd = '$(location %s) %s' % (
+    gn_utils.label_to_path(self.target.script), arg_string)
+
+    if self.use_response_file:
+      # Pipe response file contents into script
+      cmd = 'echo \'%s\' |%s%s' % (self.target.response_file_contents, NEWLINE, cmd)
+    return cmd
+
+  def get_outputs(self):
+    return self.target.outputs
+
+  def _sanitize_args(self):
+    # Handle passing parameters via response file by piping them into the script
+    # and reading them from /dev/stdin.
+
+    self.use_response_file = gn_utils.RESPONSE_FILE in self.target.args
+    if self.use_response_file:
+      # Replace {{response_file_contents}} with /dev/stdin
+      self.target.args = ['/dev/stdin' if it == gn_utils.RESPONSE_FILE else it
+                          for it in self.target.args]
+
+  def _sanitize_outputs(self):
+    pass
+
+  def sanitize(self):
+    self._sanitize_args()
+    self._sanitize_outputs()
+
+  # Whether this target generates header files
+  def is_header_generated(self):
+    return any(os.path.splitext(it)[1] == '.h' for it in self.target.outputs)
 
 class WriteBuildDateHeaderSanitizer(BaseActionSanitizer):
-  def get_args(self):
+  def _sanitize_args(self):
     self._set_arg_at(0, '$(out)')
-    return super().get_args()
+    super()._sanitize_args()
 
 class WriteBuildFlagHeaderSanitizer(BaseActionSanitizer):
-  def get_args(self):
+  def _sanitize_args(self):
     self._set_value_arg('--gen-dir', '.')
     self._set_value_arg('--output', '$(out)')
-    return super().get_args()
+    super()._sanitize_args()
 
 class JniGeneratorSanitizer(BaseActionSanitizer):
   def _add_location_tag_to_filepath(self, arg):
@@ -742,7 +786,7 @@
       arg = self._add_location_tag(arg)
     return arg
 
-  def get_args(self):
+  def _sanitize_args(self):
     self._update_value_arg('--jar_file', self._sanitize_filepath, False)
     self._update_value_arg('--jar_file', self._add_location_tag, False)
     if self._has_arg('--jar_file'):
@@ -752,10 +796,15 @@
     self._delete_value_arg('--prev_output_dir', False)
     self._update_list_arg('--input_file', self._sanitize_filepath)
     self._update_list_arg('--input_file', self._add_location_tag_to_filepath)
-    return super().get_args()
+    super()._sanitize_args()
+
+  def _sanitize_outputs(self):
+    # fix target.output directory to match #include statements.
+    self.target.outputs = {re.sub('^jni_headers/', '', out) for out in self.target.outputs}
+    super()._sanitize_outputs()
 
 class JniRegistrationGeneratorSanitizer(BaseActionSanitizer):
-  def get_args(self):
+  def _sanitize_args(self):
     self._update_value_arg('--depfile', self._sanitize_filepath)
     self._update_value_arg('--srcjar-path', self._sanitize_filepath)
     self._update_value_arg('--header-path', self._sanitize_filepath)
@@ -763,34 +812,58 @@
     # update_jni_registration_module removes them from the srcs of the module
     # It might be better to remove sources by '--sources-exclusions'
     self._delete_value_arg('--sources-exclusions')
-    return super().get_args()
+    super()._sanitize_args()
+
+  def get_cmd(self):
+    # jni_registration_generator.py doesn't work with python2
+    cmd = "python3 " + super().get_cmd()
+    # Path in the original sources file does not work in genrule.
+    # So creating sources file in cmd based on the srcs of this target.
+    # Adding ../$(current_dir)/ to the head because jni_registration_generator.py uses the files
+    # whose path startswith(..)
+    commands = ["current_dir=`basename \\\`pwd\\\``;",
+                "for f in $(in);",
+                "do",
+                "echo \\\"../$$current_dir/$$f\\\" >> $(genDir)/java.sources;",
+                "done;",
+                cmd]
+
+    # .h file jni_registration_generator.py generates has #define with directory name.
+    # With the genrule env that contains "." which is invalid. So replace that at the end of cmd.
+    commands.append(";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' ")
+    commands.append("$(genDir)/components/cronet/android/cronet_jni_registration.h")
+    return NEWLINE.join(commands)
+
+class JavaJniRegistrationGeneratorSanitizer(JniRegistrationGeneratorSanitizer):
+  def get_name(self):
+    return label_to_module_name(self.target.name) + "__java"
+
+  def _sanitize_outputs(self):
+    self.target.outputs = [out for out in self.target.outputs if
+                           out.endswith(".srcjar")]
+    super()._sanitize_outputs()
 
 class VersionSanitizer(BaseActionSanitizer):
-  def _sanitize_eval(self):
-    assert (self._is_value_arg('-e'))
-    # arg for -e EVAL option should be passed in -e PATCH_HI=int(PATCH)//256 format.
-    index = self.target.args.index('-e')
-    value = '%s=\'%s\'' % (self.target.args[index + 1], self.target.args[index + 2])
-    # escape '"' in the value
-    value = value.replace('"', r'\"')
-    self._set_arg_at(index + 1, value)
-    self.target.args.pop(index + 2)
-
-  def get_args(self):
+  def _sanitize_args(self):
     self._set_value_arg('-o', '$(out)')
     # args for the version.py contain file path without leading --arg key. So apply sanitize
     # function for all the args.
     self._update_all_args(self._sanitize_filepath_with_location_tag)
-    self._sanitize_eval()
-    return super().get_args()
+    super()._sanitize_args()
 
 class JavaCppEnumSanitizer(BaseActionSanitizer):
-  def get_args(self):
+  def _sanitize_args(self):
     self._update_all_args(self._sanitize_filepath_with_location_tag)
     self._set_value_arg('--srcjar', '$(out)')
-    return super().get_args()
+    super()._sanitize_args()
 
-def get_action_sanitizer(target):
+class MakeDafsaSanitizer(BaseActionSanitizer):
+  def is_header_generated(self):
+    # This script generates .cc files but they are #included by other sources
+    # (e.g. registry_controlled_domain.cc)
+    return True
+
+def get_action_sanitizer(target, type):
   if target.script == "//build/write_buildflag_header.py":
     return WriteBuildFlagHeaderSanitizer(target)
   elif target.script == "//build/write_build_date_header.py":
@@ -798,11 +871,16 @@
   elif target.script == '//base/android/jni_generator/jni_generator.py':
     return JniGeneratorSanitizer(target)
   elif target.script == '//base/android/jni_generator/jni_registration_generator.py':
-    return JniRegistrationGeneratorSanitizer(target)
+    if type == 'java_genrule':
+      return JavaJniRegistrationGeneratorSanitizer(target)
+    else:
+      return JniRegistrationGeneratorSanitizer(target)
   elif target.script == "//build/util/version.py":
     return VersionSanitizer(target)
   elif target.script == "//build/android/gyp/java_cpp_enum.py":
     return JavaCppEnumSanitizer(target)
+  elif target.script == "//net/tools/dafsa/make_dafsa.py":
+    return MakeDafsaSanitizer(target)
   else:
     # TODO: throw exception here once all script hacks have been converted.
     return BaseActionSanitizer(target)
@@ -841,16 +919,15 @@
   return create_action_module(blueprint, target, 'cc_genrule')
 
 def create_action_module(blueprint, target, type):
-  bp_module_name = label_to_module_name(target.name)
-  module = Module(type, bp_module_name, target.name)
-
-  sanitizer = get_action_sanitizer(target)
-  target.args = sanitizer.get_args()
+  sanitizer = get_action_sanitizer(target, type)
+  sanitizer.sanitize()
+  module = Module(type, sanitizer.get_name(), target.name)
+  module.cmd = sanitizer.get_cmd()
+  module.out = sanitizer.get_outputs()
+  if sanitizer.is_header_generated():
+    module.genrule_headers.add(module.name)
 
   if target.script == '//base/android/jni_generator/jni_generator.py':
-    # fix target.output directory to match #include statements.
-    target.outputs = [re.sub('^jni_headers/', '', out) for out in target.outputs]
-
     # android_jar.classes should be part of the tools as it list implicit classes
     # for the script to generate JNI headers.
     module.tool_files.add("base/android/jni_generator/android_jar.classes")
@@ -862,10 +939,6 @@
     target.deps.clear()
 
     target.inputs = [file for file in target.inputs if not file.startswith('//out/')]
-  elif target.script == "//net/tools/dafsa/make_dafsa.py":
-    # This script generates .cc files but source (registry_controlled_domain.cc) in the target that
-    # depends on this target includes .cc file this script generates.
-    module.genrule_headers.add(module.name)
   elif target.script == "//build/util/version.py":
     # android_chrome_version.py is not specified in anywhere but version.py imports this file
     module.tool_files.add('build/util/android_chrome_version.py')
@@ -873,26 +946,6 @@
   script = gn_utils.label_to_path(target.script)
   module.tool_files.add(script)
 
-  # Handle passing parameters via response file by piping them into the script
-  # and reading them from /dev/stdin.
-  response_file = '{{response_file_name}}'
-  use_response_file = response_file in target.args
-  if use_response_file:
-    # Replace {{response_file_contents}} with /dev/stdin
-    target.args = ['/dev/stdin' if it == response_file else it for it in target.args]
-
-  # put all args on a new line for better diffs.
-  NEWLINE = ' " +\n         "'
-  arg_string = NEWLINE.join(target.args)
-  module.cmd = '$(location %s) %s' % (script, arg_string)
-
-  if use_response_file:
-    # Pipe response file contents into script
-    module.cmd = 'echo \'%s\' |%s%s' % (target.response_file_contents, NEWLINE, module.cmd)
-
-  if any(os.path.splitext(it)[1] == '.h' for it in target.outputs):
-    module.genrule_headers.add(bp_module_name)
-
   # gn treats inputs and sources for actions equally.
   # soong only supports source files inside srcs, non-source files are added as
   # tool_files dependency.
@@ -902,33 +955,6 @@
     else:
       module.tool_files.add(gn_utils.label_to_path(it))
 
-  # Actions using template "action_with_pydeps" also put script inside inputs.
-  # TODO: it might make sense to filter inputs inside GnParser.
-  if script in module.srcs:
-    module.srcs.remove(script)
-
-  module.out.update(target.outputs)
-
-  if target.script == '//base/android/jni_generator/jni_registration_generator.py':
-    # jni_registration_generator.py doesn't work with python2
-    module.cmd = "python3 " + module.cmd
-    # Path in the original sources file does not work in genrule.
-    # So creating sources file in cmd based on the srcs of this target.
-    # Adding ../$(current_dir)/ to the head because jni_registration_generator.py uses the files
-    # whose path startswith(..)
-    commands = ["current_dir=`basename \\\`pwd\\\``;",
-                "for f in $(in);",
-                "do",
-                "echo \\\"../$$current_dir/$$f\\\" >> $(genDir)/java.sources;",
-                "done;",
-                module.cmd]
-
-    # .h file jni_registration_generator.py generates has #define with directory name.
-    # With the genrule env that contains "." which is invalid. So replace that at the end of cmd.
-    commands.append(";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' ")
-    commands.append("$(genDir)/components/cronet/android/cronet_jni_registration.h")
-    module.cmd = NEWLINE.join(commands)
-
   blueprint.add_module(module)
   return module
 
@@ -1011,15 +1037,7 @@
     if module is None:
       return None
   elif target.type == 'action':
-    if gn_utils.is_java_action(target.script, target.outputs) and \
-        target.name != "//components/cronet/android:cronet_jni_registration":
-      # This is a cc_genrule that generates both a c++ header file and
-      # srcjar for java. Treat it as a cc_genrule for the time being while
-      # making anothe deep copy of the module and append _java to it for
-      # the java_library.
-      module = create_action_module(blueprint, target, 'java_genrule')
-    else:
-      module = create_action_module(blueprint, target, 'cc_genrule')
+    module = create_action_module(blueprint, target, 'cc_genrule')
   elif target.type == 'action_foreach':
     module = create_action_foreach_modules(blueprint, target)
   elif target.type == 'copy':
@@ -1199,13 +1217,7 @@
   module = Module('java_library', bp_module_name, '//gn:java')
   module.srcs.update([gn_utils.label_to_path(source) for source in gn.java_sources])
   for dep in gn.java_actions:
-    dep_module = create_modules_from_target(blueprint, gn, dep)
-    if "cronet_jni_registration" in dep_module.name:
-      dep_module = copy.deepcopy(dep_module)
-      dep_module.name += "_java"
-      dep_module.type = 'java_genrule'
-      dep_module.out = [out for out in dep_module.out if out.endswith(".srcjar")]
-      blueprint.add_module(dep_module)
+    dep_module = create_action_module(blueprint, gn.get_target(dep), 'java_genrule')
   blueprint.add_module(module)
 
 def update_jni_registration_module(module, gn):
diff --git a/tools/gn2bp/gn_utils.py b/tools/gn2bp/gn_utils.py
index 8f33ca2..0511e8a 100644
--- a/tools/gn2bp/gn_utils.py
+++ b/tools/gn2bp/gn_utils.py
@@ -52,6 +52,8 @@
 ARCH_REGEX = r'(android_x86_64|android_x86|android_arm|android_arm64|host)'
 DEX_REGEX = '.*__dex__%s$' % ARCH_REGEX
 COMPILE_JAVA_REGEX = '.*__compile_java__%s$' % ARCH_REGEX
+RESPONSE_FILE = '{{response_file_name}}'
+
 def repo_root():
   """Returns an absolute path to the repository root."""
   return os.path.join(