Loading core/java/android/net/ConnectivityManager.java +3 −3 Original line number Diff line number Diff line Loading @@ -3286,9 +3286,9 @@ public class ConnectivityManager { /** * Called if no network is found in the timeout time specified in * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)} * without timeout. When this callback is invoked the associated * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the * requested network request cannot be fulfilled (whether or not a timeout was * specified). When this callback is invoked the associated * {@link NetworkRequest} will have already been removed and released, as if * {@link #unregisterNetworkCallback(NetworkCallback)} had been called. */ Loading core/java/android/net/NetworkFactory.java +62 −0 Original line number Diff line number Diff line Loading @@ -27,11 +27,13 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Protocol; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; /** * A NetworkFactory is an entity that creates NetworkAgent objects. Loading Loading @@ -96,7 +98,16 @@ public class NetworkFactory extends Handler { */ private static final int CMD_SET_FILTER = BASE + 3; /** * Sent by NetworkFactory to ConnectivityService to indicate that a request is * unfulfillable. * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest). */ public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4; private final Context mContext; private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>(); private AsyncChannel mAsyncChannel; private final String LOG_TAG; private final SparseArray<NetworkRequestInfo> mNetworkRequests = Loading Loading @@ -136,6 +147,36 @@ public class NetworkFactory extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { if (mAsyncChannel != null) { log("Received new connection while already connected!"); break; } if (VDBG) log("NetworkFactory fully connected"); AsyncChannel ac = new AsyncChannel(); ac.connected(null, this, msg.replyTo); ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, AsyncChannel.STATUS_SUCCESSFUL); mAsyncChannel = ac; for (Message m : mPreConnectedQueue) { ac.sendMessage(m); } mPreConnectedQueue.clear(); break; } case AsyncChannel.CMD_CHANNEL_DISCONNECT: { if (VDBG) log("CMD_CHANNEL_DISCONNECT"); if (mAsyncChannel != null) { mAsyncChannel.disconnect(); mAsyncChannel = null; } break; } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { if (DBG) log("NetworkFactory channel lost"); mAsyncChannel = null; break; } case CMD_REQUEST_NETWORK: { handleAddRequest((NetworkRequest)msg.obj, msg.arg1); break; Loading Loading @@ -267,6 +308,27 @@ public class NetworkFactory extends Handler { }); } /** * Can be called by a factory to release a request as unfulfillable: the request will be * removed, and the caller will get a * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function * returns. * * Note: this should only be called by factory which KNOWS that it is the ONLY factory which * is able to fulfill this request! */ protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) { post(() -> { if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r); Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r); if (mAsyncChannel != null) { mAsyncChannel.sendMessage(msg); } else { mPreConnectedQueue.add(msg); } }); } // override to do simple mode (request independent) protected void startNetwork() { } protected void stopNetwork() { } Loading services/core/java/com/android/server/ConnectivityService.java +32 −8 Original line number Diff line number Diff line Loading @@ -1073,7 +1073,8 @@ public class ConnectivityService extends IConnectivityManager.Stub handleRegisterNetworkRequest(new NetworkRequestInfo( null, networkRequest, new Binder())); } else { handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID); handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID, /* callOnUnavailable */ false); } } Loading Loading @@ -2701,11 +2702,25 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } private boolean maybeHandleNetworkFactoryMessage(Message msg) { switch (msg.what) { default: return false; case android.net.NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: { handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid, /* callOnUnavailable */ true); break; } } return true; } @Override public void handleMessage(Message msg) { if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg) && !maybeHandleNetworkAgentInfoMessage(msg)) { if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg) && !maybeHandleNetworkAgentInfoMessage(msg) && !maybeHandleNetworkFactoryMessage(msg)) { maybeHandleNetworkAgentMessage(msg); } } Loading Loading @@ -2870,6 +2885,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (mNetworkFactoryInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage( AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); // A network factory has connected. Send it all current NetworkRequests. for (NetworkRequestInfo nri : mNetworkRequests.values()) { if (nri.request.isListen()) continue; Loading Loading @@ -3031,7 +3049,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (existingRequest != null) { // remove the existing request. if (DBG) log("Replacing " + existingRequest.request + " with " + nri.request + " because their intents matched."); handleReleaseNetworkRequest(existingRequest.request, getCallingUid()); handleReleaseNetworkRequest(existingRequest.request, getCallingUid(), /* callOnUnavailable */ false); } handleRegisterNetworkRequest(nri); } Loading @@ -3057,7 +3076,7 @@ public class ConnectivityService extends IConnectivityManager.Stub int callingUid) { NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent); if (nri != null) { handleReleaseNetworkRequest(nri.request, callingUid); handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false); } } Loading Loading @@ -3140,7 +3159,8 @@ public class ConnectivityService extends IConnectivityManager.Stub callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0); } private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid, boolean callOnUnavailable) { final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, "release NetworkRequest"); if (nri == null) { Loading @@ -3150,6 +3170,9 @@ public class ConnectivityService extends IConnectivityManager.Stub log("releasing " + nri.request + " (release request)"); } handleRemoveNetworkRequest(nri); if (callOnUnavailable) { callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0); } } private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) { Loading Loading @@ -3651,7 +3674,8 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case EVENT_RELEASE_NETWORK_REQUEST: { handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1); handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1, /* callOnUnavailable */ false); break; } case EVENT_SET_ACCEPT_UNVALIDATED: { Loading tests/net/java/com/android/server/ConnectivityServiceTest.java +64 −1 Original line number Diff line number Diff line Loading @@ -154,6 +154,7 @@ import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; Loading Loading @@ -761,6 +762,10 @@ public class ConnectivityServiceTest { // mExpectations is non-null. private boolean mExpectingAdditions; // Used to collect the networks requests managed by this factory. This is a duplicate of // the internal information stored in the NetworkFactory (which is private). private SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>(); public MockNetworkFactory(Looper looper, Context context, String logTag, NetworkCapabilities filter) { super(looper, context, logTag, filter); Loading Loading @@ -802,6 +807,7 @@ public class ConnectivityServiceTest { } // Add the request. mNetworkRequests.put(request.requestId, request); super.handleAddRequest(request, score); // Reduce the number of request additions we're waiting for. Loading @@ -819,6 +825,7 @@ public class ConnectivityServiceTest { } // Remove the request. mNetworkRequests.remove(request.requestId); super.handleRemoveRequest(request); // Reduce the number of request removals we're waiting for. Loading @@ -828,6 +835,11 @@ public class ConnectivityServiceTest { } } // Trigger releasing the request as unfulfillable public void triggerUnfulfillable(NetworkRequest r) { super.releaseRequestAsUnfulfillableByAnyFactory(r); } private void assertNoExpectations() { if (mExpectations != null) { fail("Can't add expectation, " + mExpectations.getCount() + " already pending"); Loading Loading @@ -860,9 +872,11 @@ public class ConnectivityServiceTest { mExpectations = null; } public void waitForNetworkRequests(final int count) throws InterruptedException { public SparseArray<NetworkRequest> waitForNetworkRequests(final int count) throws InterruptedException { waitForRequests(); assertEquals(count, getMyRequestCount()); return mNetworkRequests; } } Loading Loading @@ -3636,6 +3650,55 @@ public class ConnectivityServiceTest { networkCallback.assertNoCallback(); } /** * Validate the callback flow for a factory releasing a request as unfulfillable. */ @Test public void testUnfulfillableNetworkRequest() throws Exception { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); final HandlerThread handlerThread = new HandlerThread("testUnfulfillableNetworkRequest"); handlerThread.start(); NetworkCapabilities filter = new NetworkCapabilities() .addTransportType(TRANSPORT_WIFI) .addCapability(NET_CAPABILITY_INTERNET); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter); testFactory.setScoreFilter(40); // Register the factory and expect it to receive the default request. testFactory.expectAddRequests(1); testFactory.register(); SparseArray<NetworkRequest> requests = testFactory.waitForNetworkRequests(1); assertEquals(1, requests.size()); // have 1 request at this point int origRequestId = requests.valueAt(0).requestId; // Now file the test request and expect it. testFactory.expectAddRequests(1); mCm.requestNetwork(nr, networkCallback); requests = testFactory.waitForNetworkRequests(2); // have 2 requests at this point int newRequestId = 0; for (int i = 0; i < requests.size(); ++i) { if (requests.valueAt(i).requestId != origRequestId) { newRequestId = requests.valueAt(i).requestId; break; } } // Simulate the factory releasing the request as unfulfillable and expect onUnavailable! testFactory.expectRemoveRequests(1); testFactory.triggerUnfulfillable(requests.get(newRequestId)); networkCallback.expectCallback(CallbackState.UNAVAILABLE, null); testFactory.waitForRequests(); testFactory.unregister(); handlerThread.quit(); } private static class TestKeepaliveCallback extends PacketKeepaliveCallback { public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; Loading Loading
core/java/android/net/ConnectivityManager.java +3 −3 Original line number Diff line number Diff line Loading @@ -3286,9 +3286,9 @@ public class ConnectivityManager { /** * Called if no network is found in the timeout time specified in * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)} * without timeout. When this callback is invoked the associated * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the * requested network request cannot be fulfilled (whether or not a timeout was * specified). When this callback is invoked the associated * {@link NetworkRequest} will have already been removed and released, as if * {@link #unregisterNetworkCallback(NetworkCallback)} had been called. */ Loading
core/java/android/net/NetworkFactory.java +62 −0 Original line number Diff line number Diff line Loading @@ -27,11 +27,13 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Protocol; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; /** * A NetworkFactory is an entity that creates NetworkAgent objects. Loading Loading @@ -96,7 +98,16 @@ public class NetworkFactory extends Handler { */ private static final int CMD_SET_FILTER = BASE + 3; /** * Sent by NetworkFactory to ConnectivityService to indicate that a request is * unfulfillable. * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest). */ public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4; private final Context mContext; private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>(); private AsyncChannel mAsyncChannel; private final String LOG_TAG; private final SparseArray<NetworkRequestInfo> mNetworkRequests = Loading Loading @@ -136,6 +147,36 @@ public class NetworkFactory extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { if (mAsyncChannel != null) { log("Received new connection while already connected!"); break; } if (VDBG) log("NetworkFactory fully connected"); AsyncChannel ac = new AsyncChannel(); ac.connected(null, this, msg.replyTo); ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, AsyncChannel.STATUS_SUCCESSFUL); mAsyncChannel = ac; for (Message m : mPreConnectedQueue) { ac.sendMessage(m); } mPreConnectedQueue.clear(); break; } case AsyncChannel.CMD_CHANNEL_DISCONNECT: { if (VDBG) log("CMD_CHANNEL_DISCONNECT"); if (mAsyncChannel != null) { mAsyncChannel.disconnect(); mAsyncChannel = null; } break; } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { if (DBG) log("NetworkFactory channel lost"); mAsyncChannel = null; break; } case CMD_REQUEST_NETWORK: { handleAddRequest((NetworkRequest)msg.obj, msg.arg1); break; Loading Loading @@ -267,6 +308,27 @@ public class NetworkFactory extends Handler { }); } /** * Can be called by a factory to release a request as unfulfillable: the request will be * removed, and the caller will get a * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function * returns. * * Note: this should only be called by factory which KNOWS that it is the ONLY factory which * is able to fulfill this request! */ protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) { post(() -> { if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r); Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r); if (mAsyncChannel != null) { mAsyncChannel.sendMessage(msg); } else { mPreConnectedQueue.add(msg); } }); } // override to do simple mode (request independent) protected void startNetwork() { } protected void stopNetwork() { } Loading
services/core/java/com/android/server/ConnectivityService.java +32 −8 Original line number Diff line number Diff line Loading @@ -1073,7 +1073,8 @@ public class ConnectivityService extends IConnectivityManager.Stub handleRegisterNetworkRequest(new NetworkRequestInfo( null, networkRequest, new Binder())); } else { handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID); handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID, /* callOnUnavailable */ false); } } Loading Loading @@ -2701,11 +2702,25 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } private boolean maybeHandleNetworkFactoryMessage(Message msg) { switch (msg.what) { default: return false; case android.net.NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: { handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid, /* callOnUnavailable */ true); break; } } return true; } @Override public void handleMessage(Message msg) { if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg) && !maybeHandleNetworkAgentInfoMessage(msg)) { if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg) && !maybeHandleNetworkAgentInfoMessage(msg) && !maybeHandleNetworkFactoryMessage(msg)) { maybeHandleNetworkAgentMessage(msg); } } Loading Loading @@ -2870,6 +2885,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (mNetworkFactoryInfos.containsKey(msg.replyTo)) { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage( AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); // A network factory has connected. Send it all current NetworkRequests. for (NetworkRequestInfo nri : mNetworkRequests.values()) { if (nri.request.isListen()) continue; Loading Loading @@ -3031,7 +3049,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (existingRequest != null) { // remove the existing request. if (DBG) log("Replacing " + existingRequest.request + " with " + nri.request + " because their intents matched."); handleReleaseNetworkRequest(existingRequest.request, getCallingUid()); handleReleaseNetworkRequest(existingRequest.request, getCallingUid(), /* callOnUnavailable */ false); } handleRegisterNetworkRequest(nri); } Loading @@ -3057,7 +3076,7 @@ public class ConnectivityService extends IConnectivityManager.Stub int callingUid) { NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent); if (nri != null) { handleReleaseNetworkRequest(nri.request, callingUid); handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false); } } Loading Loading @@ -3140,7 +3159,8 @@ public class ConnectivityService extends IConnectivityManager.Stub callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0); } private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid, boolean callOnUnavailable) { final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, "release NetworkRequest"); if (nri == null) { Loading @@ -3150,6 +3170,9 @@ public class ConnectivityService extends IConnectivityManager.Stub log("releasing " + nri.request + " (release request)"); } handleRemoveNetworkRequest(nri); if (callOnUnavailable) { callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0); } } private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) { Loading Loading @@ -3651,7 +3674,8 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case EVENT_RELEASE_NETWORK_REQUEST: { handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1); handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1, /* callOnUnavailable */ false); break; } case EVENT_SET_ACCEPT_UNVALIDATED: { Loading
tests/net/java/com/android/server/ConnectivityServiceTest.java +64 −1 Original line number Diff line number Diff line Loading @@ -154,6 +154,7 @@ import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; Loading Loading @@ -761,6 +762,10 @@ public class ConnectivityServiceTest { // mExpectations is non-null. private boolean mExpectingAdditions; // Used to collect the networks requests managed by this factory. This is a duplicate of // the internal information stored in the NetworkFactory (which is private). private SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>(); public MockNetworkFactory(Looper looper, Context context, String logTag, NetworkCapabilities filter) { super(looper, context, logTag, filter); Loading Loading @@ -802,6 +807,7 @@ public class ConnectivityServiceTest { } // Add the request. mNetworkRequests.put(request.requestId, request); super.handleAddRequest(request, score); // Reduce the number of request additions we're waiting for. Loading @@ -819,6 +825,7 @@ public class ConnectivityServiceTest { } // Remove the request. mNetworkRequests.remove(request.requestId); super.handleRemoveRequest(request); // Reduce the number of request removals we're waiting for. Loading @@ -828,6 +835,11 @@ public class ConnectivityServiceTest { } } // Trigger releasing the request as unfulfillable public void triggerUnfulfillable(NetworkRequest r) { super.releaseRequestAsUnfulfillableByAnyFactory(r); } private void assertNoExpectations() { if (mExpectations != null) { fail("Can't add expectation, " + mExpectations.getCount() + " already pending"); Loading Loading @@ -860,9 +872,11 @@ public class ConnectivityServiceTest { mExpectations = null; } public void waitForNetworkRequests(final int count) throws InterruptedException { public SparseArray<NetworkRequest> waitForNetworkRequests(final int count) throws InterruptedException { waitForRequests(); assertEquals(count, getMyRequestCount()); return mNetworkRequests; } } Loading Loading @@ -3636,6 +3650,55 @@ public class ConnectivityServiceTest { networkCallback.assertNoCallback(); } /** * Validate the callback flow for a factory releasing a request as unfulfillable. */ @Test public void testUnfulfillableNetworkRequest() throws Exception { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); final HandlerThread handlerThread = new HandlerThread("testUnfulfillableNetworkRequest"); handlerThread.start(); NetworkCapabilities filter = new NetworkCapabilities() .addTransportType(TRANSPORT_WIFI) .addCapability(NET_CAPABILITY_INTERNET); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter); testFactory.setScoreFilter(40); // Register the factory and expect it to receive the default request. testFactory.expectAddRequests(1); testFactory.register(); SparseArray<NetworkRequest> requests = testFactory.waitForNetworkRequests(1); assertEquals(1, requests.size()); // have 1 request at this point int origRequestId = requests.valueAt(0).requestId; // Now file the test request and expect it. testFactory.expectAddRequests(1); mCm.requestNetwork(nr, networkCallback); requests = testFactory.waitForNetworkRequests(2); // have 2 requests at this point int newRequestId = 0; for (int i = 0; i < requests.size(); ++i) { if (requests.valueAt(i).requestId != origRequestId) { newRequestId = requests.valueAt(i).requestId; break; } } // Simulate the factory releasing the request as unfulfillable and expect onUnavailable! testFactory.expectRemoveRequests(1); testFactory.triggerUnfulfillable(requests.get(newRequestId)); networkCallback.expectCallback(CallbackState.UNAVAILABLE, null); testFactory.waitForRequests(); testFactory.unregister(); handlerThread.quit(); } private static class TestKeepaliveCallback extends PacketKeepaliveCallback { public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; Loading