Snap for 12100605 from 74d887508e415139efb43cf311aae382789eff0a to mainline-os-statsd-release

Change-Id: Iff5f90c40ea585e8b779ec92f89b65c4962cad68
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index dd55165..c8e43d0 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -288,7 +288,6 @@
     private final boolean mDrop802_3Frames;
     private final int[] mEthTypeBlackList;
 
-    private final Clock mClock;
     private final ApfCounterTracker mApfCounterTracker = new ApfCounterTracker();
     @GuardedBy("this")
     private final long mSessionStartMs;
@@ -389,21 +388,13 @@
     public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
             IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics) {
         this(context, config, ifParams, ipClientCallback, networkQuirkMetrics,
-                new Dependencies(context), new Clock());
+                new Dependencies(context));
     }
 
     @VisibleForTesting
     public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
             IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics,
             Dependencies dependencies) {
-        this(context, config, ifParams, ipClientCallback, networkQuirkMetrics, dependencies,
-                new Clock());
-    }
-
-    @VisibleForTesting
-    public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
-            IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics,
-            Dependencies dependencies, Clock clock) {
         mApfVersionSupported = config.apfVersionSupported;
         mApfRamSize = config.apfRamSize;
         mInstallableProgramSizeClamp = config.installableProgramSizeClamp;
@@ -429,8 +420,7 @@
         mNetworkQuirkMetrics = networkQuirkMetrics;
         mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics();
         mApfSessionInfoMetrics = dependencies.getApfSessionInfoMetrics();
-        mClock = clock;
-        mSessionStartMs = mClock.elapsedRealtime();
+        mSessionStartMs = dependencies.elapsedRealtime();
         mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs;
         mHasClat = config.hasClatInterface;
 
@@ -455,7 +445,9 @@
 
         mDependencies.onApfFilterCreated(this);
         // mReceiveThread is created in startFilter() and halted in shutdown().
-        mDependencies.onThreadCreated(mReceiveThread);
+        if (mReceiveThread != null) {
+            mDependencies.onThreadCreated(mReceiveThread);
+        }
     }
 
     /**
@@ -468,6 +460,31 @@
             mContext = context;
         }
 
+        /**
+         * Create a socket to read RAs.
+         */
+        @Nullable
+        public FileDescriptor createRaReaderSocket(int ifIndex) {
+            FileDescriptor socket;
+            try {
+                socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0);
+                NetworkStackUtils.attachRaFilter(socket);
+                SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, ifIndex);
+                Os.bind(socket, addr);
+            } catch (SocketException | ErrnoException e) {
+                Log.wtf(TAG, "Error starting filter", e);
+                return null;
+            }
+            return socket;
+        }
+
+        /**
+         * Get elapsedRealtime.
+         */
+        public long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
         /** Add receiver for detecting doze mode change */
         public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver) {
             final IntentFilter intentFilter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
@@ -621,24 +638,17 @@
             // Install basic filters
             installNewProgramLocked();
         }
-        FileDescriptor socket;
-        try {
-            socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0);
-            NetworkStackUtils.attachRaFilter(socket);
-            SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index);
-            Os.bind(socket, addr);
-        } catch(SocketException|ErrnoException e) {
-            Log.wtf(TAG, "Error starting filter", e);
-            return;
+        FileDescriptor socket = mDependencies.createRaReaderSocket(mInterfaceParams.index);
+        if (socket != null) {
+            mReceiveThread = new ReceiveThread(socket);
+            mReceiveThread.start();
         }
-        mReceiveThread = new ReceiveThread(socket);
-        mReceiveThread.start();
     }
 
     // Returns seconds since device boot.
     @VisibleForTesting
     protected int secondsSinceBoot() {
-        return (int) (mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS);
+        return (int) (mDependencies.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS);
     }
 
     public static class InvalidRaException extends Exception {
@@ -2248,7 +2258,7 @@
      */
     @GuardedBy("this")
     @VisibleForTesting
-    protected ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException {
+    public ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException {
         // This is guaranteed to succeed because of the check in maybeCreate.
         ApfV4GeneratorBase<?> gen;
         if (shouldUseApfV6Generator()) {
@@ -2559,7 +2569,7 @@
 
     private synchronized void collectAndSendMetrics() {
         if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return;
-        final long sessionDurationMs = mClock.elapsedRealtime() - mSessionStartMs;
+        final long sessionDurationMs = mDependencies.elapsedRealtime() - mSessionStartMs;
         if (sessionDurationMs < mMinMetricsSessionDurationMs) return;
 
         // Collect and send IpClientRaInfoMetrics.
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt
index d9bc6ab..938bf25 100644
--- a/tests/unit/src/android/net/apf/ApfFilterTest.kt
+++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -39,6 +39,7 @@
 import android.net.apf.BaseApfGenerator.APF_VERSION_6
 import android.net.ip.IpClient.IpClientCallbacksWrapper
 import android.os.Build
+import android.os.SystemClock
 import android.system.OsConstants.IFA_F_TENTATIVE
 import androidx.test.filters.SmallTest
 import com.android.internal.annotations.GuardedBy
@@ -171,6 +172,7 @@
                 mApfFilterCreated.add(invocation.getArgument(0))
             }
         }.`when`(dependencies).onApfFilterCreated(any())
+        `when`(dependencies.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime())
     }
 
     private fun shutdownApfFilters() {
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 9c36df7..c80aaa9 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -17,6 +17,7 @@
 package android.net.apf;
 
 import static android.net.apf.ApfCounterTracker.Counter.getCounterEnumFromOffset;
+import static android.net.apf.ApfTestHelpers.consumeInstalledProgram;
 import static android.net.apf.BaseApfGenerator.APF_VERSION_3;
 import static android.net.apf.BaseApfGenerator.APF_VERSION_4;
 import static android.net.apf.BaseApfGenerator.APF_VERSION_6;
@@ -34,6 +35,7 @@
 import static android.net.apf.ApfTestUtils.assertProgramEquals;
 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
 import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
+import static android.system.OsConstants.AF_UNIX;
 import static android.system.OsConstants.ETH_P_ARP;
 import static android.system.OsConstants.ETH_P_IP;
 import static android.system.OsConstants.ETH_P_IPV6;
@@ -41,6 +43,7 @@
 import static android.system.OsConstants.IPPROTO_IPV6;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_STREAM;
 
 import static com.android.net.module.util.HexDump.hexStringToByteArray;
 import static com.android.net.module.util.HexDump.toHexString;
@@ -49,14 +52,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -70,14 +76,15 @@
 import android.net.TcpKeepalivePacketDataParcelable;
 import android.net.apf.ApfCounterTracker.Counter;
 import android.net.apf.ApfFilter.ApfConfiguration;
-import android.net.apf.ApfTestUtils.MockIpClientCallback;
-import android.net.apf.ApfTestUtils.TestApfFilter;
 import android.net.apf.BaseApfGenerator.IllegalInstructionException;
+import android.net.ip.IpClient;
 import android.net.metrics.IpConnectivityLog;
 import android.os.Build;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.stats.connectivity.NetworkQuirkEvent;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -92,6 +99,7 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.DnsPacket;
 import com.android.net.module.util.Inet4AddressUtils;
+import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.net.module.util.PacketBuilder;
 import com.android.networkstack.metrics.ApfSessionInfoMetrics;
@@ -102,6 +110,7 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
+import libcore.io.IoUtils;
 import libcore.io.Streams;
 
 import org.junit.After;
@@ -117,6 +126,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -165,12 +175,15 @@
     @Mock private NetworkQuirkMetrics mNetworkQuirkMetrics;
     @Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics;
     @Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics;
-    @Mock private ApfFilter.Clock mClock;
+    @Mock private IpClient.IpClientCallbacksWrapper mIpClientCb;
     @GuardedBy("mApfFilterCreated")
     private final ArrayList<AndroidPacketFilter> mApfFilterCreated = new ArrayList<>();
     @GuardedBy("mThreadsToBeCleared")
     private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>();
 
+    private FileDescriptor mWriteSocket;
+    private long mCurrentTimeMs;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -189,13 +202,20 @@
             }
             return null;
         }).when(mDependencies).onThreadCreated(any());
+        FileDescriptor readSocket = new FileDescriptor();
+        mWriteSocket = new FileDescriptor();
+        Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
+        doReturn(readSocket).when(mDependencies).createRaReaderSocket(anyInt());
+        mCurrentTimeMs = SystemClock.elapsedRealtime();
+        doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime();
+        doReturn(true).when(mIpClientCb).installPacketFilter(any());
     }
 
     private void quitThreads() throws Exception {
         ConcurrentUtils.quitThreads(
                 THREAD_QUIT_MAX_RETRY_COUNT,
                 false /* interrupt */,
-                HANDLER_TIMEOUT_MS,
+                TIMEOUT_MS,
                 () -> {
                     synchronized (mThreadsToBeCleared) {
                         final ArrayList<Thread> ret = new ArrayList<>(mThreadsToBeCleared);
@@ -231,6 +251,8 @@
         shutdownApfFilters();
         // Clear mocks to prevent from stubs holding instances and cause memory leaks.
         Mockito.framework().clearInlineMocks();
+        IoUtils.closeQuietly(mWriteSocket);
+        mWriteSocket = null;
     }
 
     private static final String TAG = "ApfTest";
@@ -245,7 +267,8 @@
     private static final int MIN_RDNSS_LIFETIME_SEC = 0;
     private static final int MIN_METRICS_SESSION_DURATIONS_MS = 300_000;
 
-    private static final int HANDLER_TIMEOUT_MS = 1000;
+    private static final int TIMEOUT_MS = 1000;
+    private static final int NO_CALLBACK_TIMEOUT_MS = 500;
     private static final int THREAD_QUIT_MAX_RETRY_COUNT = 3;
 
     // Constants for opcode encoding
@@ -258,6 +281,11 @@
     private static final byte SIZE32  = (byte)(3 << 1);
     private static final byte R1_REG = 1;
 
+    private static final byte[] TEST_MAC_ADDR = {2, 3, 4, 5, 6, 7};
+    private static final int TEST_IFACE_IDX = 1234;
+    private static final InterfaceParams TEST_PARAMS = new InterfaceParams("lo", TEST_IFACE_IDX,
+            MacAddress.fromBytes(TEST_MAC_ADDR), 1500 /* defaultMtu */);
+
     private static ApfConfiguration getDefaultConfig() {
         ApfFilter.ApfConfiguration config = new ApfConfiguration();
         config.apfVersionSupported = 2;
@@ -1006,6 +1034,11 @@
         }
     }
 
+    private void pretendPacketReceived(byte[] packet)
+            throws IOException, ErrnoException {
+        Os.write(mWriteSocket, packet, 0, packet.length);
+    }
+
     /**
      * Generate APF program, run pcap file though APF filter, then check all the packets in the file
      * should be dropped.
@@ -1014,7 +1047,6 @@
     public void testApfFilterPcapFile() throws Exception {
         final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
         String pcapFilename = stageFile(R.raw.apfPcap);
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
@@ -1024,10 +1056,11 @@
         config.apfRamSize = 1700;
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
-                mNetworkQuirkMetrics, mDependencies);
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS,
+                mIpClientCb, mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 2 /* installCnt */);
         apfFilter.setLinkProperties(lp);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         byte[] data = new byte[Counter.totalSize()];
         final boolean result;
 
@@ -1193,18 +1226,18 @@
 
     @Test
     public void testApfFilterIPv4() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
 
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
-                mNetworkQuirkMetrics, mDependencies);
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS,
+                mIpClientCb, mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         apfFilter.setLinkProperties(lp);
 
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
         if (SdkLevel.isAtLeastV()) {
@@ -1216,7 +1249,7 @@
         }
 
         // Verify unicast IPv4 packet is passed
-        put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+        put(packet, ETH_DEST_ADDR_OFFSET, TEST_MAC_ADDR);
         packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
         put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
         assertPass(program, packet.array());
@@ -1244,21 +1277,20 @@
         assertDrop(program, packet.array());
 
         // Verify broadcast IPv4 DHCP to us is passed
-        put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+        put(packet, DHCP_CLIENT_MAC_OFFSET, TEST_MAC_ADDR);
         assertPass(program, packet.array());
 
         // Verify unicast IPv4 DHCP to us is passed
-        put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+        put(packet, ETH_DEST_ADDR_OFFSET, TEST_MAC_ADDR);
         assertPass(program, packet.array());
     }
 
     @Test
     public void testApfFilterIPv6() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify empty IPv6 packet is passed
         ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP);
@@ -1664,18 +1696,18 @@
         final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
         final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
 
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
 
         ApfConfiguration config = getDefaultConfig();
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         apfFilter.setLinkProperties(lp);
 
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Construct IPv4 and IPv6 multicast packets.
         ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP);
@@ -1697,7 +1729,7 @@
 
         // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
         ByteBuffer bcastv4unicastl2packet = makeIpv4Packet(IPPROTO_UDP);
-        bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
+        bcastv4unicastl2packet.put(TEST_MAC_ADDR);
         bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
         put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
 
@@ -1709,9 +1741,8 @@
         assertPass(program, bcastv4unicastl2packet.array());
 
         // Turn on multicast filter and verify it works
-        ipClientCallback.resetApfProgramWait();
         apfFilter.setMulticastFilter(true);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, mcastv4packet.array());
         assertDrop(program, mcastv6packet.array());
         assertDrop(program, bcastv4packet1.array());
@@ -1719,9 +1750,8 @@
         assertDrop(program, bcastv4unicastl2packet.array());
 
         // Turn off multicast filter and verify it's off
-        ipClientCallback.resetApfProgramWait();
         apfFilter.setMulticastFilter(false);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertPass(program, mcastv4packet.array());
         assertPass(program, mcastv6packet.array());
         assertPass(program, bcastv4packet1.array());
@@ -1729,13 +1759,14 @@
         assertPass(program, bcastv4unicastl2packet.array());
 
         // Verify it can be initialized to on
-        ipClientCallback.resetApfProgramWait();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
-                mDependencies);
+        clearInvocations(mIpClientCb);
+        apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         apfFilter.setLinkProperties(lp);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, mcastv4packet.array());
         assertDrop(program, mcastv6packet.array());
         assertDrop(program, bcastv4packet1.array());
@@ -1758,10 +1789,10 @@
     }
 
     private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration configuration = getDefaultConfig();
-        final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback,
-                configuration, mNetworkQuirkMetrics, mDependencies);
+        final ApfFilter apfFilter = new ApfFilter(mContext, configuration, TEST_PARAMS,
+                mIpClientCb, mNetworkQuirkMetrics, mDependencies);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture());
@@ -1774,7 +1805,7 @@
         put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
 
         // Normally, we let multicast pings alone...
-        assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+        assertPass(program, packet.array());
 
         if (isLightDozing) {
             doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
@@ -1783,19 +1814,21 @@
             doReturn(true).when(mPowerManager).isDeviceIdleMode();
             receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
         }
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         // ...and even while dozing...
-        assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+        assertPass(program, packet.array());
 
         // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
         apfFilter.setMulticastFilter(true);
-        assertDrop(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        assertDrop(program, packet.array());
 
         // However, we should still let through all other ICMPv6 types.
         ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
         setIpv6VersionFields(packet);
         packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6);
         raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
-        assertPass(ipClientCallback.assertProgramUpdateAndGet(), raPacket.array());
+        assertPass(program, raPacket.array());
 
         // Now wake up from doze mode to ensure that we no longer drop the packets.
         // (The multicast filter is still enabled at this point).
@@ -1806,17 +1839,17 @@
             doReturn(false).when(mPowerManager).isDeviceIdleMode();
             receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
         }
-        assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        assertPass(program, packet.array());
     }
 
     @Test
     @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public void testApfFilter802_3() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
-        ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify empty packet of 100 zero bytes is passed
         // Note that eth-type = 0 makes it an IEEE802.3 frame
@@ -1832,11 +1865,10 @@
         assertPass(program, packet.array());
 
         // Now turn on the filter
-        ipClientCallback.resetApfProgramWait();
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+        apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify that IEEE802.3 frame is dropped
         // In this case ethtype is used for payload length
@@ -1859,11 +1891,10 @@
         final int[] ipv4BlackList = {ETH_P_IP};
         final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
 
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
-        ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify empty packet of 100 zero bytes is passed
         // Note that eth-type = 0 makes it an IEEE802.3 frame
@@ -1879,11 +1910,10 @@
         assertPass(program, packet.array());
 
         // Now add IPv4 to the black list
-        ipClientCallback.resetApfProgramWait();
         config.ethTypeBlackList = ipv4BlackList;
-        apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+        apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify that IPv4 frame will be dropped
         setIpv4VersionFields(packet);
@@ -1894,11 +1924,10 @@
         assertPass(program, packet.array());
 
         // Now let us have both IPv4 and IPv6 in the black list
-        ipClientCallback.resetApfProgramWait();
         config.ethTypeBlackList = ipv4Ipv6BlackList;
-        apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+        apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify that IPv4 frame will be dropped
         setIpv4VersionFields(packet);
@@ -1909,12 +1938,6 @@
         assertDrop(program, packet.array());
     }
 
-    private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) {
-        cb.resetApfProgramWait();
-        filter.setLinkProperties(lp);
-        return cb.assertProgramUpdateAndGet();
-    }
-
     private void verifyArpFilter(byte[] program, int filterResult) {
         // Verify ARP request packet
         assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
@@ -1938,24 +1961,28 @@
 
     @Test
     public void testApfFilterArp() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify initially ARP request filter is off, and GARP filter is on.
-        verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS);
+        verifyArpFilter(program, PASS);
 
         // Inform ApfFilter of our address and verify ARP filtering is on
         LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
         LinkProperties lp = new LinkProperties();
         assertTrue(lp.addLinkAddress(linkAddress));
-        verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);
+        apfFilter.setLinkProperties(lp);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        verifyArpFilter(program, DROP);
 
+        apfFilter.setLinkProperties(new LinkProperties());
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         // Inform ApfFilter of loss of IP and verify ARP filtering is off
-        verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);
+        verifyArpFilter(program, PASS);
     }
 
     private static byte[] arpReply(byte[] sip, byte[] tip) {
@@ -1997,12 +2024,12 @@
 
     @Test
     public void testApfFilterKeepaliveAck() throws Exception {
-        final MockIpClientCallback cb = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         byte[] program;
         final int srcPort = 12345;
         final int dstPort = 54321;
@@ -2031,7 +2058,7 @@
         parcel.ack = ackNum;
 
         apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
-        program = cb.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify IPv4 keepalive ack packet is dropped
         // src: 10.0.0.6, port: 54321
@@ -2053,6 +2080,7 @@
 
         // Remove IPv4 keepalive filter
         apfFilter.removeKeepalivePacketFilter(slot1);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         try {
             // src: 2404:0:0:0:0:0:faf1, port: 12345
@@ -2070,7 +2098,7 @@
             ipv6Parcel.ack = ackNum;
 
             apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel);
-            program = cb.assertProgramUpdateAndGet();
+            program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
             // Verify IPv6 keepalive ack packet is dropped
             // src: 2404:0:0:0:0:0:faf2, port: 54321
@@ -2093,7 +2121,7 @@
             // Verify multiple filters
             apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
             apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel);
-            program = cb.assertProgramUpdateAndGet();
+            program = consumeInstalledProgram(mIpClientCb, 3 /* installCnt */);
 
             // Verify IPv4 keepalive ack packet is dropped
             // src: 10.0.0.6, port: 54321
@@ -2132,8 +2160,6 @@
             // TODO: support V6 packets
         }
 
-        program = cb.assertProgramUpdateAndGet();
-
         // Verify IPv4, IPv6 packets are passed
         assertPass(program,
                 ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
@@ -2189,12 +2215,12 @@
 
     @Test
     public void testApfFilterNattKeepalivePacket() throws Exception {
-        final MockIpClientCallback cb = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         byte[] program;
         final int srcPort = 1024;
         final int dstPort = 4500;
@@ -2215,7 +2241,7 @@
         parcel.dstPort = dstPort;
 
         apfFilter.addNattKeepalivePacketFilter(slot1, parcel);
-        program = cb.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Verify IPv4 keepalive packet is dropped
         // src: 10.0.0.6, port: 4500
@@ -2440,10 +2466,9 @@
 
     @Test
     public void testRaToString() throws Exception {
-        MockIpClientCallback cb = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics,
-                mDependencies);
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
 
         byte[] packet = buildLargeRa();
         ApfFilter.Ra ra = apfFilter.new Ra(packet, packet.length);
@@ -2493,29 +2518,32 @@
 
     // Test that when ApfFilter is shown the given packet, it generates a program to filter it
     // for the given lifetime.
-    private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
-            ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
+    private byte[] verifyRaLifetime(ByteBuffer packet, int lifetime)
+            throws IOException, ErrnoException {
         // Verify new program generated if ApfFilter witnesses RA
-        apfFilter.pretendPacketReceived(packet.array());
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        clearInvocations(mIpClientCb);
+        pretendPacketReceived(packet.array());
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         verifyRaLifetime(program, packet, lifetime);
+        return program;
     }
 
-    private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
-            ByteBuffer packet) throws IOException, ErrnoException {
-        apfFilter.pretendPacketReceived(packet.array());
-        ipClientCallback.assertNoProgramUpdate();
+    private void assertInvalidRa(ByteBuffer packet)
+            throws IOException, ErrnoException, InterruptedException {
+        clearInvocations(mIpClientCb);
+        pretendPacketReceived(packet.array());
+        Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
+        verify(mIpClientCb, never()).installPacketFilter(any());
     }
 
     @Test
     public void testApfFilterRa() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         final int ROUTER_LIFETIME = 1000;
         final int PREFIX_VALID_LIFETIME = 200;
@@ -2530,7 +2558,7 @@
         ByteBuffer basePacket = ByteBuffer.wrap(ra.build());
         assertPass(program, basePacket.array());
 
-        verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
+        verifyRaLifetime(basePacket, ROUTER_LIFETIME);
 
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         // Check that changes are ignored in every byte of the flow label.
@@ -2542,7 +2570,7 @@
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         ra.addZeroLengthOption();
         ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(ra.build());
-        assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);
+        assertInvalidRa(zeroLengthOptionPacket);
 
         // Generate several RAs with different options and lifetimes, and verify when
         // ApfFilter is shown these packets, it generates programs to filter them for the
@@ -2550,43 +2578,39 @@
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         ra.addPioOption(PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, "2001:db8::/64");
         ByteBuffer prefixOptionPacket = ByteBuffer.wrap(ra.build());
-        verifyRaLifetime(
-                apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+        verifyRaLifetime(prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
 
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         ra.addRdnssOption(RDNSS_LIFETIME, "2001:4860:4860::8888", "2001:4860:4860::8844");
         ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(ra.build());
-        verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
+        verifyRaLifetime(rdnssOptionPacket, RDNSS_LIFETIME);
 
         final int lowLifetime = 60;
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         ra.addRdnssOption(lowLifetime, "2620:fe::9");
         ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap(ra.build());
-        verifyRaLifetime(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket,
-                ROUTER_LIFETIME);
+        verifyRaLifetime(lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME);
 
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/96");
         ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(ra.build());
-        verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
+        program = verifyRaLifetime(routeInfoOptionPacket, ROUTE_LIFETIME);
 
         // Check that RIOs differing only in the first 4 bytes are different.
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/64");
         // Packet should be passed because it is different.
-        program = ipClientCallback.assertProgramUpdateAndGet();
         assertPass(program, ra.build());
 
         ra = new RaPacketBuilder(ROUTER_LIFETIME);
         ra.addDnsslOption(DNSSL_LIFETIME, "test.example.com", "one.more.example.com");
         ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(ra.build());
-        verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
+        verifyRaLifetime(dnsslOptionPacket, ROUTER_LIFETIME);
 
         ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa());
-        verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300);
+        program = verifyRaLifetime(largeRaPacket, 300);
 
         // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10).
-        program = ipClientCallback.assertProgramUpdateAndGet();
         verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
         verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
         verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
@@ -2599,13 +2623,12 @@
 
     @Test
     public void testRaWithDifferentReachableTimeAndRetransTimer() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         final int RA_REACHABLE_TIME = 1800;
         final int RA_RETRANSMISSION_TIMER = 1234;
 
@@ -2619,8 +2642,8 @@
         assertPass(program, raPacket);
 
         // Assume apf is shown the given RA, it generates program to filter it.
-        apfFilter.pretendPacketReceived(raPacket);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(raPacket);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, raPacket);
 
         // A packet with different reachable time should be passed.
@@ -2641,13 +2664,12 @@
     @SuppressWarnings("ByteBufferBackingArray")
     @Test
     public void testRaWithProgramInstalledSomeTimeAfterLastSeen() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         final int routerLifetime = 1000;
         final int timePassedSeconds = 12;
@@ -2655,26 +2677,32 @@
         // Verify that when the program is generated and installed some time after RA is last seen
         // it should be installed with the correct remaining lifetime.
         ByteBuffer basePacket = ByteBuffer.wrap(new RaPacketBuilder(routerLifetime).build());
-        verifyRaLifetime(apfFilter, ipClientCallback, basePacket, routerLifetime);
-        apfFilter.increaseCurrentTimeSeconds(timePassedSeconds);
+        verifyRaLifetime(basePacket, routerLifetime);
+
+        mCurrentTimeMs += timePassedSeconds * DateUtils.SECOND_IN_MILLIS;
+        doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime();
         synchronized (apfFilter) {
             apfFilter.installNewProgramLocked();
         }
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds);
 
         // Packet should be passed if the program is installed after 1/6 * lifetime from last seen
-        apfFilter.increaseCurrentTimeSeconds((int) (routerLifetime / 6) - timePassedSeconds - 1);
+        mCurrentTimeMs +=
+                ((routerLifetime / 6) - timePassedSeconds - 1) * DateUtils.SECOND_IN_MILLIS;
+        doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime();
         synchronized (apfFilter) {
             apfFilter.installNewProgramLocked();
         }
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, basePacket.array());
-        apfFilter.increaseCurrentTimeSeconds(1);
+
+        mCurrentTimeMs += DateUtils.SECOND_IN_MILLIS;
+        doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime();
         synchronized (apfFilter) {
             apfFilter.installNewProgramLocked();
         }
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertPass(program, basePacket.array());
     }
 
@@ -2709,12 +2737,11 @@
     public void testRaParsing() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
-        MockIpClientCallback cb = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics,
-                mDependencies);
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);
@@ -2731,12 +2758,11 @@
     public void testRaProcessing() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
-        MockIpClientCallback cb = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
         config.multicastFilter = DROP_MULTICAST;
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics,
-                mDependencies);
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);
@@ -2750,34 +2776,34 @@
 
     @Test
     public void testMatchedRaUpdatesLifetime() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, getDefaultConfig(),
-                ipClientCallback, mNetworkQuirkMetrics, mDependencies);
+        final ApfFilter apfFilter = new ApfFilter(mContext, getDefaultConfig(), TEST_PARAMS,
+                mIpClientCb, mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an RA and build an APF program
         byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // lifetime dropped significantly, assert pass
         ra = new RaPacketBuilder(200 /* router lifetime */).build();
         assertPass(program, ra);
 
         // update program with the new RA
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // assert program was updated and new lifetimes were taken into account.
         assertDrop(program, ra);
     }
-
     @Test
     public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        TestApfFilter apfFilter;
+        ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         // Template packet:
         // Frame 1: 150 bytes on wire (1200 bits), 150 bytes captured (1200 bits)
         // Ethernet II, Src: Netgear_23:67:2c (28:c6:8e:23:67:2c), Dst: IPv6mcast_01 (33:33:00:00:00:01)
@@ -2825,13 +2851,11 @@
         final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0";
         final List<String> lifetimes = List.of("FFFFFFFF", "00000000", "00000001", "00001B58");
         for (String lifetime : lifetimes) {
-            apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
-                    mDependencies);
             final byte[] ra = hexStringToByteArray(
                     String.format(packetStringFmt, lifetime + lifetime));
             // feed the RA into APF and generate the filter, the filter shouldn't crash.
-            apfFilter.pretendPacketReceived(ra);
-            ipClientCallback.assertProgramUpdateAndGet();
+            pretendPacketReceived(ra);
+            consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         }
     }
 
@@ -2839,20 +2863,20 @@
     // Old lifetime is 0
     @Test
     public void testAcceptRaMinLftCase1a() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an initial RA and build an APF program
         byte[] ra = new RaPacketBuilder(1800 /* router lifetime */)
                 .addPioOption(1800 /*valid*/, 0 /*preferred*/, "2001:db8::/64")
                 .build();
 
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // repeated RA is dropped
         assertDrop(program, ra);
@@ -2868,20 +2892,20 @@
     // Old lifetime is > 0
     @Test
     public void testAcceptRaMinLftCase2a() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an initial RA and build an APF program
         byte[] ra = new RaPacketBuilder(1800 /* router lifetime */)
                 .addPioOption(1800 /*valid*/, 100 /*preferred*/, "2001:db8::/64")
                 .build();
 
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // repeated RA is dropped
         assertDrop(program, ra);
@@ -2904,18 +2928,18 @@
     // Old lifetime is 0
     @Test
     public void testAcceptRaMinLftCase1b() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an initial RA and build an APF program
         byte[] ra = new RaPacketBuilder(0 /* router lifetime */).build();
 
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // repeated RA is dropped
         assertDrop(program, ra);
@@ -2929,23 +2953,22 @@
         assertPass(program, ra);
     }
 
-
     // Test for go/apf-ra-filter Case 2b.
     // Old lifetime is < accept_ra_min_lft (but not 0).
     @Test
     public void testAcceptRaMinLftCase2b() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an initial RA and build an APF program
         byte[] ra = new RaPacketBuilder(100 /* router lifetime */).build();
 
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // repeated RA is dropped
         assertDrop(program, ra);
@@ -2971,18 +2994,18 @@
     // Old lifetime is >= accept_ra_min_lft and <= 3 * accept_ra_min_lft
     @Test
     public void testAcceptRaMinLftCase3b() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an initial RA and build an APF program
         byte[] ra = new RaPacketBuilder(200 /* router lifetime */).build();
 
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // repeated RA is dropped
         assertDrop(program, ra);
@@ -3004,18 +3027,18 @@
     // Old lifetime is > 3 * accept_ra_min_lft
     @Test
     public void testAcceptRaMinLftCase4b() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an initial RA and build an APF program
         byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
 
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // repeated RA is dropped
         assertDrop(program, ra);
@@ -3043,17 +3066,17 @@
 
     @Test
     public void testRaFilterIsUpdated() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         // configure accept_ra_min_lft
         final ApfConfiguration config = getDefaultConfig();
         config.acceptRaMinLft = 180;
-        final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
                 mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Create an initial RA and build an APF program
         byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
-        apfFilter.pretendPacketReceived(ra);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // repeated RA is dropped.
         assertDrop(program, ra);
@@ -3061,38 +3084,38 @@
         // updated RA is passed, repeated RA is dropped after program update.
         ra = new RaPacketBuilder(599 /* router lifetime */).build();
         assertPass(program, ra);
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, ra);
 
         ra = new RaPacketBuilder(180 /* router lifetime */).build();
         assertPass(program, ra);
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, ra);
 
         ra = new RaPacketBuilder(0 /* router lifetime */).build();
         assertPass(program, ra);
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, ra);
 
         ra = new RaPacketBuilder(180 /* router lifetime */).build();
         assertPass(program, ra);
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, ra);
 
         ra = new RaPacketBuilder(599 /* router lifetime */).build();
         assertPass(program, ra);
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, ra);
 
         ra = new RaPacketBuilder(1800 /* router lifetime */).build();
         assertPass(program, ra);
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         assertDrop(program, ra);
     }
 
@@ -3115,18 +3138,12 @@
         assertEquals(want, got);
     }
 
-    private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config,
-            MockIpClientCallback ipClientCallback) throws Exception {
-        return new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
-                    mDependencies, mClock);
-    }
-
-
     @Test
     public void testInstallPacketFilterFailure() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false);
+        doReturn(false).when(mIpClientCb).installPacketFilter(any());
         final ApfConfiguration config = getDefaultConfig();
-        final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
         verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
         verify(mNetworkQuirkMetrics).statsWrite();
         reset(mNetworkQuirkMetrics);
@@ -3140,28 +3157,27 @@
 
     @Test
     public void testApfProgramOverSize() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         config.apfVersionSupported = 2;
         config.apfRamSize = 512;
-        final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         final byte[] ra = buildLargeRa();
-        apfFilter.pretendPacketReceived(ra);
+        pretendPacketReceived(ra);
         // The generated program size will be 529, which is larger than 512
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
         verify(mNetworkQuirkMetrics).statsWrite();
     }
 
     @Test
     public void testGenerateApfProgramException() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
-        final TestAndroidPacketFilter apfFilter;
-        apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
-                mDependencies, true /* throwsExceptionWhenGeneratesProgram */);
+        ApfFilter apfFilter = spy(new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies));
         synchronized (apfFilter) {
+            when(apfFilter.emitPrologueLocked()).thenThrow(new IllegalStateException("test"));
             apfFilter.installNewProgramLocked();
         }
         verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
@@ -3170,17 +3186,17 @@
 
     @Test
     public void testApfSessionInfoMetrics() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         config.apfVersionSupported = 4;
         config.apfRamSize = 4096;
         final long startTimeMs = 12345;
         final long durationTimeMs = config.minMetricsSessionDurationMs;
-        doReturn(startTimeMs).when(mClock).elapsedRealtime();
-        final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
+        doReturn(startTimeMs).when(mDependencies).elapsedRealtime();
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
+        byte[] program = consumeInstalledProgram(mIpClientCb, 2 /* installCnt */);
         int maxProgramSize = 0;
         int numProgramUpdated = 0;
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
         maxProgramSize = Math.max(maxProgramSize, program.length);
         numProgramUpdated++;
 
@@ -3197,14 +3213,14 @@
         expectedData[totalPacketsCounterIdx + 3] += 1;
         expectedData[passedIpv6IcmpCounterIdx + 3] += 1;
         assertDataMemoryContentsIgnoreVersion(PASS, program, ra, data, expectedData);
-        apfFilter.pretendPacketReceived(ra);
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra);
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         maxProgramSize = Math.max(maxProgramSize, program.length);
         numProgramUpdated++;
 
         apfFilter.setMulticastFilter(true);
         // setMulticastFilter will trigger program installation.
-        program = ipClientCallback.assertProgramUpdateAndGet();
+        program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
         maxProgramSize = Math.max(maxProgramSize, program.length);
         numProgramUpdated++;
 
@@ -3221,7 +3237,7 @@
         apfFilter.setDataSnapshot(data);
 
         // Write metrics data to statsd pipeline when shutdown.
-        doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime();
+        doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime();
         apfFilter.shutdown();
         verify(mApfSessionInfoMetrics).setVersion(4);
         verify(mApfSessionInfoMetrics).setMemorySize(4096);
@@ -3250,13 +3266,13 @@
 
     @Test
     public void testIpClientRaInfoMetrics() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         final long startTimeMs = 12345;
         final long durationTimeMs = config.minMetricsSessionDurationMs;
-        doReturn(startTimeMs).when(mClock).elapsedRealtime();
-        final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+        doReturn(startTimeMs).when(mDependencies).elapsedRealtime();
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         final int routerLifetime = 1000;
         final int prefixValidLifetime = 200;
@@ -3293,23 +3309,24 @@
 
         // Inject RA packets. Calling assertProgramUpdateAndGet()/assertNoProgramUpdate() is to make
         // sure that the RA packet has been processed.
-        apfFilter.pretendPacketReceived(ra1.build());
-        program = ipClientCallback.assertProgramUpdateAndGet();
-        apfFilter.pretendPacketReceived(ra2.build());
-        program = ipClientCallback.assertProgramUpdateAndGet();
-        apfFilter.pretendPacketReceived(raInvalid.build());
-        ipClientCallback.assertNoProgramUpdate();
-        apfFilter.pretendPacketReceived(raZeroRouterLifetime.build());
-        ipClientCallback.assertProgramUpdateAndGet();
-        apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build());
-        ipClientCallback.assertProgramUpdateAndGet();
-        apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build());
-        ipClientCallback.assertProgramUpdateAndGet();
-        apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build());
-        ipClientCallback.assertProgramUpdateAndGet();
+        pretendPacketReceived(ra1.build());
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        pretendPacketReceived(ra2.build());
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        pretendPacketReceived(raInvalid.build());
+        Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
+        verify(mIpClientCb, never()).installPacketFilter(any());
+        pretendPacketReceived(raZeroRouterLifetime.build());
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        pretendPacketReceived(raZeroPioValidLifetime.build());
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        pretendPacketReceived(raZeroRdnssLifetime.build());
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
+        pretendPacketReceived(raZeroRioRouteLifetime.build());
+        consumeInstalledProgram(mIpClientCb, 1 /* installCnt */);
 
         // Write metrics data to statsd pipeline when shutdown.
-        doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime();
+        doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime();
         apfFilter.shutdown();
 
         // Verify each metric fields in IpClientRaInfoMetrics.
@@ -3324,26 +3341,25 @@
     }
 
     private void verifyNoMetricsWrittenForShortDuration(boolean isLegacy) throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         final ApfConfiguration config = getDefaultConfig();
         final long startTimeMs = 12345;
         final long durationTimeMs = config.minMetricsSessionDurationMs;
 
         // Verify no metrics data written to statsd for duration less than durationTimeMs.
-        doReturn(startTimeMs).when(mClock).elapsedRealtime();
-        final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
-        doReturn(startTimeMs + durationTimeMs - 1).when(mClock).elapsedRealtime();
+        doReturn(startTimeMs).when(mDependencies).elapsedRealtime();
+        final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
+        doReturn(startTimeMs + durationTimeMs - 1).when(mDependencies).elapsedRealtime();
         apfFilter.shutdown();
         verify(mApfSessionInfoMetrics, never()).statsWrite();
         verify(mIpClientRaInfoMetrics, never()).statsWrite();
 
         // Verify metrics data written to statsd for duration greater than or equal to
         // durationTimeMs.
-        ApfFilter.Clock clock = mock(ApfFilter.Clock.class);
-        doReturn(startTimeMs).when(clock).elapsedRealtime();
-        final TestAndroidPacketFilter apfFilter2 = new TestApfFilter(mContext, config,
-                ipClientCallback, mNetworkQuirkMetrics, mDependencies, clock);
-        doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime();
+        doReturn(startTimeMs).when(mDependencies).elapsedRealtime();
+        final ApfFilter apfFilter2 = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb,
+                mNetworkQuirkMetrics, mDependencies);
+        doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime();
         apfFilter2.shutdown();
         verify(mApfSessionInfoMetrics).statsWrite();
         verify(mIpClientRaInfoMetrics).statsWrite();
diff --git a/tests/unit/src/android/net/apf/ApfTestHelpers.kt b/tests/unit/src/android/net/apf/ApfTestHelpers.kt
index b67ec05..30d5813 100644
--- a/tests/unit/src/android/net/apf/ApfTestHelpers.kt
+++ b/tests/unit/src/android/net/apf/ApfTestHelpers.kt
@@ -74,6 +74,7 @@
             return ret
         }
 
+        @JvmStatic
         fun consumeInstalledProgram(
             ipClientCb: IpClient.IpClientCallbacksWrapper,
             installCnt: Int
diff --git a/tests/unit/src/android/net/apf/ApfTestUtils.java b/tests/unit/src/android/net/apf/ApfTestUtils.java
index e2a1b55..76fa5c9 100644
--- a/tests/unit/src/android/net/apf/ApfTestUtils.java
+++ b/tests/unit/src/android/net/apf/ApfTestUtils.java
@@ -26,18 +26,13 @@
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.MacAddress;
 import android.net.apf.BaseApfGenerator.IllegalInstructionException;
 import android.net.ip.IIpClientCallbacks;
 import android.net.ip.IpClient;
 import android.net.metrics.IpConnectivityLog;
 import android.os.ConditionVariable;
-import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.text.format.DateUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.HexDump;
@@ -50,7 +45,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.net.InetAddress;
 import java.util.Arrays;
 
 /**
@@ -289,129 +283,6 @@
     }
 
     /**
-     * The test apf filter class.
-     */
-    public static class TestApfFilter extends ApfFilter implements TestAndroidPacketFilter {
-        public static final byte[] MOCK_MAC_ADDR = {2, 3, 4, 5, 6, 7};
-        private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
-
-        private static final InterfaceParams LOCAL_PARAMS = InterfaceParams.getByName("lo");
-
-        private FileDescriptor mWriteSocket;
-        private long mCurrentTimeMs = SystemClock.elapsedRealtime();
-        private final MockIpClientCallback mMockIpClientCb;
-        private final boolean mThrowsExceptionWhenGeneratesProgram;
-
-        public TestApfFilter(Context context, ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics,
-                Dependencies dependencies) throws Exception {
-            this(context, config, ipClientCallback, networkQuirkMetrics, dependencies,
-                    false /* throwsExceptionWhenGeneratesProgram */, new ApfFilter.Clock());
-        }
-
-        public TestApfFilter(Context context, ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics,
-                Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram)
-                throws Exception {
-            this(context, config, ipClientCallback, networkQuirkMetrics, dependencies,
-                    throwsExceptionWhenGeneratesProgram, new ApfFilter.Clock());
-        }
-
-        public TestApfFilter(Context context, ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics,
-                Dependencies dependencies, ApfFilter.Clock clock) throws Exception {
-            this(context, config, ipClientCallback, networkQuirkMetrics, dependencies,
-                    false /* throwsExceptionWhenGeneratesProgram */, clock);
-        }
-
-        public TestApfFilter(Context context, ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics,
-                Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram,
-                ApfFilter.Clock clock) throws Exception {
-            super(context, config, new InterfaceParams("lo", LOCAL_PARAMS.index,
-                            MacAddress.fromBytes(MOCK_MAC_ADDR)), ipClientCallback,
-                    networkQuirkMetrics, dependencies, clock);
-            mMockIpClientCb = ipClientCallback;
-            mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram;
-        }
-
-        /**
-         * Create a new test ApfFiler.
-         */
-        public static ApfFilter createTestApfFilter(Context context,
-                MockIpClientCallback ipClientCallback, ApfConfiguration config,
-                NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies)
-                throws Exception {
-            LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
-            LinkProperties lp = new LinkProperties();
-            lp.addLinkAddress(link);
-            TestApfFilter apfFilter = new TestApfFilter(context, config, ipClientCallback,
-                    networkQuirkMetrics, dependencies);
-            apfFilter.setLinkProperties(lp);
-            return apfFilter;
-        }
-
-        /**
-         * Pretend an RA packet has been received and show it to ApfFilter.
-         */
-        public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
-            mMockIpClientCb.resetApfProgramWait();
-            // ApfFilter's ReceiveThread will be waiting to read this.
-            Os.write(mWriteSocket, packet, 0, packet.length);
-        }
-
-        /**
-         * Simulate current time changes.
-         */
-        public void increaseCurrentTimeSeconds(int delta) {
-            mCurrentTimeMs += delta * DateUtils.SECOND_IN_MILLIS;
-        }
-
-        @Override
-        protected int secondsSinceBoot() {
-            return (int) (mCurrentTimeMs / DateUtils.SECOND_IN_MILLIS);
-        }
-
-        @Override
-        public synchronized void startFilter() {
-            installNewProgramLocked();
-
-            // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
-            FileDescriptor readSocket = new FileDescriptor();
-            mWriteSocket = new FileDescriptor();
-            try {
-                Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
-            } catch (ErrnoException e) {
-                fail();
-                return;
-            }
-            // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
-            // This allows us to pretend RA packets have been received via pretendPacketReceived().
-            mReceiveThread = new ReceiveThread(readSocket);
-            mReceiveThread.start();
-        }
-
-        @Override
-        public synchronized void shutdown() {
-            super.shutdown();
-            if (mReceiveThread != null) {
-                mReceiveThread.halt();
-                mReceiveThread = null;
-            }
-            IoUtils.closeQuietly(mWriteSocket);
-        }
-
-        @Override
-        @GuardedBy("this")
-        protected ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException {
-            if (mThrowsExceptionWhenGeneratesProgram) {
-                throw new IllegalStateException();
-            }
-            return super.emitPrologueLocked();
-        }
-    }
-
-    /**
      * The test legacy apf filter class.
      */
     public static class TestLegacyApfFilter extends LegacyApfFilter