From 0c04b7478112b96a5376046dddeee10084a61bf0 Mon Sep 17 00:00:00 2001 From: Erik Kline Date: Thu, 7 Jul 2016 16:50:58 +0900 Subject: [PATCH] Support requesting async LinkProperties/NetworkCapabilities updates Bug: 9580643 Change-Id: I1d7ba7645c20d7d53f6eef777dfce43727940f13 --- .../java/android/net/ConnectivityManager.java | 40 +++ .../android/net/IConnectivityManager.aidl | 2 + .../android/server/ConnectivityService.java | 250 +++++++++++------- .../server/ConnectivityServiceTest.java | 68 ++++- 4 files changed, 270 insertions(+), 90 deletions(-) diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 8d4137957b..3c2ac6733c 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1034,6 +1034,26 @@ public class ConnectivityManager { } } + /** + * Request that this callback be invoked at ConnectivityService's earliest + * convenience with the current satisfying network's LinkProperties. + * If no such network exists no callback invocation is performed. + * + * The callback must have been registered with #requestNetwork() or + * #registerDefaultNetworkCallback(); callbacks registered with + * registerNetworkCallback() are not specific to any particular Network so + * do not cause any updates. + * + * @hide + */ + public void requestLinkProperties(NetworkCallback networkCallback) { + try { + mService.requestLinkProperties(networkCallback.networkRequest); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This * will return {@code null} if the network is unknown. @@ -1051,6 +1071,26 @@ public class ConnectivityManager { } } + /** + * Request that this callback be invoked at ConnectivityService's earliest + * convenience with the current satisfying network's NetworkCapabilities. + * If no such network exists no callback invocation is performed. + * + * The callback must have been registered with #requestNetwork() or + * #registerDefaultNetworkCallback(); callbacks registered with + * registerNetworkCallback() are not specific to any particular Network so + * do not cause any updates. + * + * @hide + */ + public void requestNetworkCapabilities(NetworkCallback networkCallback) { + try { + mService.requestNetworkCapabilities(networkCallback.networkRequest); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Gets the URL that should be used for resolving whether a captive portal is present. * 1. This URL should respond with a 204 response to a GET request to indicate no captive diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 0d518cc140..d48c155986 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -156,6 +156,8 @@ interface IConnectivityManager void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, in PendingIntent operation); + void requestLinkProperties(in NetworkRequest networkRequest); + void requestNetworkCapabilities(in NetworkRequest networkRequest); void releaseNetworkRequest(in NetworkRequest networkRequest); void setAcceptUnvalidated(in Network network, boolean accept, boolean always); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index b12a9619e5..985d4a811a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -303,7 +303,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * indicates a timeout period is over - check if we had a network yet or not - * and if not, call the timeout calback (but leave the request live until they + * and if not, call the timeout callback (but leave the request live until they * cancel it. * includes a NetworkRequestInfo */ @@ -380,6 +380,16 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31; + /** + * Indicates a caller has requested to have its callback invoked with + * the latest LinkProperties or NetworkCapabilities. + * + * arg1 = UID of caller + * obj = NetworkRequest + */ + private static final int EVENT_REQUEST_LINKPROPERTIES = 32; + private static final int EVENT_REQUEST_NETCAPABILITIES = 33; + /** Handler thread used for both of the handlers below. */ @VisibleForTesting protected final HandlerThread mHandlerThread; @@ -2447,106 +2457,146 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { - NetworkRequestInfo nri = mNetworkRequests.get(request); + private NetworkRequestInfo getNriForAppRequest( + NetworkRequest request, int callingUid, String requestedOperation) { + final NetworkRequestInfo nri = mNetworkRequests.get(request); + if (nri != null) { if (Process.SYSTEM_UID != callingUid && nri.mUid != callingUid) { - if (DBG) log("Attempt to release unowned NetworkRequest " + request); - return; + log(String.format("UID %d attempted to %s for unowned request %s", + callingUid, requestedOperation, nri)); + return null; } - if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request); - nri.unlinkDeathRecipient(); - mNetworkRequests.remove(request); - synchronized (mUidToNetworkRequestCount) { - int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); - if (requests < 1) { - Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " + - nri.mUid); - } else if (requests == 1) { - mUidToNetworkRequestCount.removeAt( - mUidToNetworkRequestCount.indexOfKey(nri.mUid)); + } + + return nri; + } + + private void handleRequestCallbackUpdate(NetworkRequest request, int callingUid, + String description, int callbackType) { + final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, description); + if (nri == null) return; + + final NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); + // The network that is satisfying this request may have changed since + // the application requested the update. + // + // - If the request is no longer satisfied, don't send any updates. + // - If the request is satisfied by a different network, it is the + // caller's responsibility to check that the Network object in the + // callback matches the network that was returned in the last + // onAvailable() callback for this request. + if (nai == null) return; + callCallbackForRequest(nri, nai, callbackType, 0); + } + + private void handleRequestLinkProperties(NetworkRequest request, int callingUid) { + handleRequestCallbackUpdate(request, callingUid, + "request LinkProperties", ConnectivityManager.CALLBACK_IP_CHANGED); + } + + private void handleRequestNetworkCapabilities(NetworkRequest request, int callingUid) { + handleRequestCallbackUpdate(request, callingUid, + "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED); + } + + private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { + final NetworkRequestInfo nri = getNriForAppRequest( + request, callingUid, "release NetworkRequest"); + if (nri == null) return; + + if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request); + nri.unlinkDeathRecipient(); + mNetworkRequests.remove(request); + synchronized (mUidToNetworkRequestCount) { + int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); + if (requests < 1) { + Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " + + nri.mUid); + } else if (requests == 1) { + mUidToNetworkRequestCount.removeAt( + mUidToNetworkRequestCount.indexOfKey(nri.mUid)); + } else { + mUidToNetworkRequestCount.put(nri.mUid, requests - 1); + } + } + mNetworkRequestInfoLogs.log("RELEASE " + nri); + if (nri.request.isRequest()) { + boolean wasKept = false; + NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); + if (nai != null) { + nai.removeRequest(nri.request.requestId); + if (VDBG) { + log(" Removing from current network " + nai.name() + + ", leaving " + nai.numNetworkRequests() + " requests."); + } + // If there are still lingered requests on this network, don't tear it down, + // but resume lingering instead. + updateLingerState(nai, SystemClock.elapsedRealtime()); + if (unneeded(nai)) { + if (DBG) log("no live requests for " + nai.name() + "; disconnecting"); + teardownUnneededNetwork(nai); } else { - mUidToNetworkRequestCount.put(nri.mUid, requests - 1); + wasKept = true; + } + mNetworkForRequestId.remove(nri.request.requestId); + } + + // TODO: remove this code once we know that the Slog.wtf is never hit. + // + // Find all networks that are satisfying this request and remove the request + // from their request lists. + // TODO - it's my understanding that for a request there is only a single + // network satisfying it, so this loop is wasteful + for (NetworkAgentInfo otherNai : mNetworkAgentInfos.values()) { + if (otherNai.isSatisfyingRequest(nri.request.requestId) && otherNai != nai) { + Slog.wtf(TAG, "Request " + nri.request + " satisfied by " + + otherNai.name() + ", but mNetworkAgentInfos says " + + (nai != null ? nai.name() : "null")); } } - mNetworkRequestInfoLogs.log("RELEASE " + nri); - if (nri.request.isRequest()) { - boolean wasKept = false; - NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); - if (nai != null) { - nai.removeRequest(nri.request.requestId); - if (VDBG) { - log(" Removing from current network " + nai.name() + - ", leaving " + nai.numNetworkRequests() + " requests."); - } - // If there are still lingered requests on this network, don't tear it down, - // but resume lingering instead. - updateLingerState(nai, SystemClock.elapsedRealtime()); - if (unneeded(nai)) { - if (DBG) log("no live requests for " + nai.name() + "; disconnecting"); - teardownUnneededNetwork(nai); - } else { - wasKept = true; - } - mNetworkForRequestId.remove(nri.request.requestId); - } - // TODO: remove this code once we know that the Slog.wtf is never hit. - // - // Find all networks that are satisfying this request and remove the request - // from their request lists. - // TODO - it's my understanding that for a request there is only a single - // network satisfying it, so this loop is wasteful - for (NetworkAgentInfo otherNai : mNetworkAgentInfos.values()) { - if (otherNai.isSatisfyingRequest(nri.request.requestId) && otherNai != nai) { - Slog.wtf(TAG, "Request " + nri.request + " satisfied by " + - otherNai.name() + ", but mNetworkAgentInfos says " + - (nai != null ? nai.name() : "null")); - } - } - - // Maintain the illusion. When this request arrived, we might have pretended - // that a network connected to serve it, even though the network was already - // connected. Now that this request has gone away, we might have to pretend - // that the network disconnected. LegacyTypeTracker will generate that - // phantom disconnect for this type. - if (nri.request.legacyType != TYPE_NONE && nai != null) { - boolean doRemove = true; - if (wasKept) { - // check if any of the remaining requests for this network are for the - // same legacy type - if so, don't remove the nai - for (int i = 0; i < nai.numNetworkRequests(); i++) { - NetworkRequest otherRequest = nai.requestAt(i); - if (otherRequest.legacyType == nri.request.legacyType && - otherRequest.isRequest()) { - if (DBG) log(" still have other legacy request - leaving"); - doRemove = false; - } + // Maintain the illusion. When this request arrived, we might have pretended + // that a network connected to serve it, even though the network was already + // connected. Now that this request has gone away, we might have to pretend + // that the network disconnected. LegacyTypeTracker will generate that + // phantom disconnect for this type. + if (nri.request.legacyType != TYPE_NONE && nai != null) { + boolean doRemove = true; + if (wasKept) { + // check if any of the remaining requests for this network are for the + // same legacy type - if so, don't remove the nai + for (int i = 0; i < nai.numNetworkRequests(); i++) { + NetworkRequest otherRequest = nai.requestAt(i); + if (otherRequest.legacyType == nri.request.legacyType && + otherRequest.isRequest()) { + if (DBG) log(" still have other legacy request - leaving"); + doRemove = false; } } - - if (doRemove) { - mLegacyTypeTracker.remove(nri.request.legacyType, nai, false); - } } - for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, - nri.request); - } - } else { - // listens don't have a singular affectedNetwork. Check all networks to see - // if this listen request applies and remove it. - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { - nai.removeRequest(nri.request.requestId); - if (nri.request.networkCapabilities.hasSignalStrength() && - nai.satisfiesImmutableCapabilitiesOf(nri.request)) { - updateSignalStrengthThresholds(nai, "RELEASE", nri.request); - } + if (doRemove) { + mLegacyTypeTracker.remove(nri.request.legacyType, nai, false); + } + } + + for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { + nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, + nri.request); + } + } else { + // listens don't have a singular affectedNetwork. Check all networks to see + // if this listen request applies and remove it. + for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + nai.removeRequest(nri.request.requestId); + if (nri.request.networkCapabilities.hasSignalStrength() && + nai.satisfiesImmutableCapabilitiesOf(nri.request)) { + updateSignalStrengthThresholds(nai, "RELEASE", nri.request); } } - callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0); } + callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0); } @Override @@ -2709,6 +2759,12 @@ public class ConnectivityService extends IConnectivityManager.Stub handleMobileDataAlwaysOn(); break; } + case EVENT_REQUEST_LINKPROPERTIES: + handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1); + break; + case EVENT_REQUEST_NETCAPABILITIES: + handleRequestNetworkCapabilities((NetworkRequest) msg.obj, msg.arg1); + break; // Sent by KeepaliveTracker to process an app request on the state machine thread. case NetworkAgent.CMD_START_PACKET_KEEPALIVE: { mKeepaliveTracker.handleStartKeepalive(msg); @@ -4169,11 +4225,27 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); } + @Override + public void requestLinkProperties(NetworkRequest networkRequest) { + ensureNetworkRequestHasType(networkRequest); + if (networkRequest.type == NetworkRequest.Type.LISTEN) return; + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_REQUEST_LINKPROPERTIES, getCallingUid(), 0, networkRequest)); + } + + @Override + public void requestNetworkCapabilities(NetworkRequest networkRequest) { + ensureNetworkRequestHasType(networkRequest); + if (networkRequest.type == NetworkRequest.Type.LISTEN) return; + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_REQUEST_NETCAPABILITIES, getCallingUid(), 0, networkRequest)); + } + @Override public void releaseNetworkRequest(NetworkRequest networkRequest) { ensureNetworkRequestHasType(networkRequest); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(), - 0, networkRequest)); + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(), 0, networkRequest)); } @Override diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index ba77b03c5e..f2a93155d3 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -1040,6 +1040,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { enum CallbackState { NONE, AVAILABLE, + NETWORK_CAPABILITIES, + LINK_PROPERTIES, LOSING, LOST } @@ -1072,18 +1074,21 @@ public class ConnectivityServiceTest extends AndroidTestCase { } private final LinkedBlockingQueue mCallbacks = new LinkedBlockingQueue<>(); - private void setLastCallback(CallbackState state, Network network, Object o) { + protected void setLastCallback(CallbackState state, Network network, Object o) { mCallbacks.offer(new CallbackInfo(state, network, o)); } + @Override public void onAvailable(Network network) { setLastCallback(CallbackState.AVAILABLE, network, null); } + @Override public void onLosing(Network network, int maxMsToLive) { setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */); } + @Override public void onLost(Network network) { setLastCallback(CallbackState.LOST, network, null); } @@ -1744,6 +1749,67 @@ public class ConnectivityServiceTest extends AndroidTestCase { defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); } + private class TestRequestUpdateCallback extends TestNetworkCallback { + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) { + setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap); + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) { + setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp); + } + } + + @LargeTest + public void testRequestCallbackUpdates() throws Exception { + // File a network request for mobile. + final TestNetworkCallback cellNetworkCallback = new TestRequestUpdateCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + + // Bring up the mobile network. + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + // We should get onAvailable(). + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + // We should get onCapabilitiesChanged(), when the mobile network successfully validates. + cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + + // Update LinkProperties. + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("foonet_data0"); + mCellNetworkAgent.sendLinkProperties(lp); + // We should get onLinkPropertiesChanged(). + cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + + // Register a garden variety default network request. + final TestNetworkCallback dfltNetworkCallback = new TestRequestUpdateCallback(); + mCm.registerDefaultNetworkCallback(dfltNetworkCallback); + // Only onAvailable() is called; no other information is delivered. + dfltNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + dfltNetworkCallback.assertNoCallback(); + + // Request a NetworkCapabilities update; only the requesting callback is notified. + mCm.requestNetworkCapabilities(dfltNetworkCallback); + dfltNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + dfltNetworkCallback.assertNoCallback(); + + // Request a LinkProperties update; only the requesting callback is notified. + mCm.requestLinkProperties(dfltNetworkCallback); + dfltNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + dfltNetworkCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(dfltNetworkCallback); + mCm.unregisterNetworkCallback(cellNetworkCallback); + } + @SmallTest public void testRequestBenchmark() throws Exception { // Benchmarks connecting and switching performance in the presence of a large number of