Support requesting async LinkProperties/NetworkCapabilities updates

am: 0c04b74781

Change-Id: Icc048323debbbe641941c5c0d993f4c9782c5d3e
This commit is contained in:
Erik Kline
2016-07-11 11:01:42 +00:00
committed by android-build-merger
4 changed files with 270 additions and 90 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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<CallbackInfo> 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