diff options
| author | 2019-01-08 12:09:18 -0800 | |
|---|---|---|
| committer | 2019-02-13 08:44:51 -0800 | |
| commit | ae574a86738cb28bef7e23ed754ccaffd807b1c5 (patch) | |
| tree | defef98c18efabdfd81494726f1b9eeb245f15a7 | |
| parent | 00aa3faa09eabb0582fc0eea065b5ad59b3bde32 (diff) | |
[CS] Support "instant failure" from factories
Add a mechanism by which a factory can declare "instant failure" for
a request - which would result in it getting an OnUnavailable()
(even without a timeout).
Factories may only do this iff:
1. They know they are the only factory which may fulfill this
request (common for transport-specific requests).
2. The know that the request can definitely not be
fulfilled at any point in the future.
Bug: 31382922
Test: atest ConnectivityServiceTest
Change-Id: I9bce0f4d85fa8cad7f8a9998819f945b778c5ac5
4 files changed, 161 insertions, 12 deletions
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 2aca55aacf7a..34311cb4ff50 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3138,9 +3138,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. */ diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java index 0dfe7a495738..5b1d12c603b4 100644 --- a/core/java/android/net/NetworkFactory.java +++ b/core/java/android/net/NetworkFactory.java @@ -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; /** @@ -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 = @@ -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; @@ -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() { } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d1cd072ee215..e78322b0abfc 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1048,7 +1048,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); } } @@ -2629,11 +2630,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); } } @@ -2780,6 +2795,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; @@ -2948,7 +2966,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); } @@ -2974,7 +2993,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); } } @@ -3057,7 +3076,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) { @@ -3067,6 +3087,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) { @@ -3457,7 +3480,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: { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index a7c95c78d05d..65e83143212f 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -153,6 +153,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; @@ -747,6 +748,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); @@ -799,6 +804,7 @@ public class ConnectivityServiceTest { } // Add the request. + mNetworkRequests.put(request.requestId, request); super.handleAddRequest(request, score, factorySerialNumber); mExpectations.notify(); } @@ -816,11 +822,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"); @@ -860,9 +872,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; } } @@ -3523,6 +3537,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 }; |