Loading core/java/android/net/ConnectivityManager.java +3 −3 Original line number Diff line number Diff line Loading @@ -3147,9 +3147,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; import java.util.concurrent.atomic.AtomicInteger; /** Loading Loading @@ -113,7 +115,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 @@ -155,6 +166,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, msg.arg2); break; Loading Loading @@ -355,6 +396,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 @@ -1060,7 +1060,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 @@ -2641,11 +2642,25 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } private boolean maybeHandleNetworkFactoryMessage(Message msg) { switch (msg.what) { default: return false; case 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 @@ -2787,6 +2802,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 @@ -2957,7 +2975,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 @@ -2983,7 +3002,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 @@ -3066,7 +3085,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 @@ -3076,6 +3096,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 @@ -3507,7 +3530,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 com.android.internal.net.VpnConfig; import com.android.internal.util.ArrayUtils; Loading Loading @@ -748,6 +749,10 @@ public class ConnectivityServiceTest { // mExpectations is non-empty. 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 @@ -800,6 +805,7 @@ public class ConnectivityServiceTest { } // Add the request. mNetworkRequests.put(request.requestId, request); super.handleAddRequest(request, score, factorySerialNumber); mExpectations.notify(); } Loading @@ -817,11 +823,17 @@ public class ConnectivityServiceTest { } // Remove the request. mNetworkRequests.remove(request.requestId); super.handleRemoveRequest(request); mExpectations.notify(); } } // Trigger releasing the request as unfulfillable public void triggerUnfulfillable(NetworkRequest r) { super.releaseRequestAsUnfulfillableByAnyFactory(r); } private void assertNoExpectations() { if (mExpectations.size() != 0) { fail("Can't add expectation, " + mExpectations.size() + " already pending"); Loading Loading @@ -861,9 +873,11 @@ public class ConnectivityServiceTest { assertEquals(msg, 0, count); } 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 @@ -3533,6 +3547,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.expectAddRequestsWithScores(0); 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.expectAddRequestsWithScores(0); 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 @@ -3147,9 +3147,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; import java.util.concurrent.atomic.AtomicInteger; /** Loading Loading @@ -113,7 +115,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 @@ -155,6 +166,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, msg.arg2); break; Loading Loading @@ -355,6 +396,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 @@ -1060,7 +1060,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 @@ -2641,11 +2642,25 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } private boolean maybeHandleNetworkFactoryMessage(Message msg) { switch (msg.what) { default: return false; case 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 @@ -2787,6 +2802,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 @@ -2957,7 +2975,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 @@ -2983,7 +3002,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 @@ -3066,7 +3085,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 @@ -3076,6 +3096,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 @@ -3507,7 +3530,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 com.android.internal.net.VpnConfig; import com.android.internal.util.ArrayUtils; Loading Loading @@ -748,6 +749,10 @@ public class ConnectivityServiceTest { // mExpectations is non-empty. 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 @@ -800,6 +805,7 @@ public class ConnectivityServiceTest { } // Add the request. mNetworkRequests.put(request.requestId, request); super.handleAddRequest(request, score, factorySerialNumber); mExpectations.notify(); } Loading @@ -817,11 +823,17 @@ public class ConnectivityServiceTest { } // Remove the request. mNetworkRequests.remove(request.requestId); super.handleRemoveRequest(request); mExpectations.notify(); } } // Trigger releasing the request as unfulfillable public void triggerUnfulfillable(NetworkRequest r) { super.releaseRequestAsUnfulfillableByAnyFactory(r); } private void assertNoExpectations() { if (mExpectations.size() != 0) { fail("Can't add expectation, " + mExpectations.size() + " already pending"); Loading Loading @@ -861,9 +873,11 @@ public class ConnectivityServiceTest { assertEquals(msg, 0, count); } 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 @@ -3533,6 +3547,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.expectAddRequestsWithScores(0); 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.expectAddRequestsWithScores(0); 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