Support timeouts for requestNetwork() invocations.

(cherry-pick of 0b7a74842b)
(cherry picked from commit 155a59aa63)

Bug: 21414325
Change-Id: I1a58823a372154589f972b98c4c428eab0e0523e
This commit is contained in:
Erik Kline
2015-11-25 12:49:38 +09:00
committed by Lorenzo Colitti
parent e3b646f25d
commit d99da81fae
3 changed files with 169 additions and 49 deletions

View File

@@ -2079,8 +2079,6 @@ public class ConnectivityManager {
@SystemApi @SystemApi
public void startTethering(int type, boolean showProvisioningUi, public void startTethering(int type, boolean showProvisioningUi,
final OnStartTetheringCallback callback, Handler handler) { final OnStartTetheringCallback callback, Handler handler) {
checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
ResultReceiver wrappedCallback = new ResultReceiver(handler) { ResultReceiver wrappedCallback = new ResultReceiver(handler) {
@Override @Override
protected void onReceiveResult(int resultCode, Bundle resultData) { protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -2091,7 +2089,6 @@ public class ConnectivityManager {
} }
} }
}; };
try { try {
mService.startTethering(type, wrappedCallback, showProvisioningUi); mService.startTethering(type, wrappedCallback, showProvisioningUi);
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -2660,7 +2657,6 @@ public class ConnectivityManager {
public static final int CALLBACK_IP_CHANGED = BASE + 7; public static final int CALLBACK_IP_CHANGED = BASE + 7;
/** @hide */ /** @hide */
public static final int CALLBACK_RELEASED = BASE + 8; public static final int CALLBACK_RELEASED = BASE + 8;
// TODO: consider deleting CALLBACK_EXIT and shifting following enum codes down by 1.
/** @hide */ /** @hide */
public static final int CALLBACK_EXIT = BASE + 9; public static final int CALLBACK_EXIT = BASE + 9;
/** @hide obj = NetworkCapabilities, arg1 = seq number */ /** @hide obj = NetworkCapabilities, arg1 = seq number */
@@ -2691,17 +2687,24 @@ public class ConnectivityManager {
} }
private class CallbackHandler extends Handler { private class CallbackHandler extends Handler {
private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
private final AtomicInteger mRefCount;
private static final String TAG = "ConnectivityManager.CallbackHandler"; private static final String TAG = "ConnectivityManager.CallbackHandler";
private final ConnectivityManager mCm;
private static final boolean DBG = false; private static final boolean DBG = false;
CallbackHandler(Looper looper) { CallbackHandler(Looper looper, HashMap<NetworkRequest, NetworkCallback>callbackMap,
AtomicInteger refCount, ConnectivityManager cm) {
super(looper); super(looper);
mCallbackMap = callbackMap;
mRefCount = refCount;
mCm = cm;
} }
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
NetworkRequest request = getObject(message, NetworkRequest.class); NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class);
Network network = getObject(message, Network.class); Network network = (Network) getObject(message, Network.class);
if (DBG) { if (DBG) {
Log.d(TAG, whatToString(message.what) + " for network " + network); Log.d(TAG, whatToString(message.what) + " for network " + network);
} }
@@ -2744,7 +2747,9 @@ public class ConnectivityManager {
case CALLBACK_CAP_CHANGED: { case CALLBACK_CAP_CHANGED: {
NetworkCallback callback = getCallback(request, "CAP_CHANGED"); NetworkCallback callback = getCallback(request, "CAP_CHANGED");
if (callback != null) { if (callback != null) {
NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); NetworkCapabilities cap = (NetworkCapabilities)getObject(message,
NetworkCapabilities.class);
callback.onCapabilitiesChanged(network, cap); callback.onCapabilitiesChanged(network, cap);
} }
break; break;
@@ -2752,7 +2757,9 @@ public class ConnectivityManager {
case CALLBACK_IP_CHANGED: { case CALLBACK_IP_CHANGED: {
NetworkCallback callback = getCallback(request, "IP_CHANGED"); NetworkCallback callback = getCallback(request, "IP_CHANGED");
if (callback != null) { if (callback != null) {
LinkProperties lp = getObject(message, LinkProperties.class); LinkProperties lp = (LinkProperties)getObject(message,
LinkProperties.class);
callback.onLinkPropertiesChanged(network, lp); callback.onLinkPropertiesChanged(network, lp);
} }
break; break;
@@ -2772,16 +2779,24 @@ public class ConnectivityManager {
break; break;
} }
case CALLBACK_RELEASED: { case CALLBACK_RELEASED: {
final NetworkCallback callback; NetworkCallback callback = null;
synchronized(sCallbacks) { synchronized(mCallbackMap) {
callback = sCallbacks.remove(request); callback = mCallbackMap.remove(request);
} }
if (callback == null) { if (callback != null) {
synchronized(mRefCount) {
if (mRefCount.decrementAndGet() == 0) {
getLooper().quit();
}
}
} else {
Log.e(TAG, "callback not found for RELEASED message"); Log.e(TAG, "callback not found for RELEASED message");
} }
break; break;
} }
case CALLBACK_EXIT: { case CALLBACK_EXIT: {
Log.d(TAG, "Listener quitting");
getLooper().quit();
break; break;
} }
case EXPIRE_LEGACY_REQUEST: { case EXPIRE_LEGACY_REQUEST: {
@@ -2791,14 +2806,14 @@ public class ConnectivityManager {
} }
} }
private <T> T getObject(Message msg, Class<T> c) { private Object getObject(Message msg, Class c) {
return (T) msg.getData().getParcelable(c.getSimpleName()); return msg.getData().getParcelable(c.getSimpleName());
} }
private NetworkCallback getCallback(NetworkRequest req, String name) { private NetworkCallback getCallback(NetworkRequest req, String name) {
NetworkCallback callback; NetworkCallback callback;
synchronized(sCallbacks) { synchronized(mCallbackMap) {
callback = sCallbacks.get(req); callback = mCallbackMap.get(req);
} }
if (callback == null) { if (callback == null) {
Log.e(TAG, "callback not found for " + name + " message"); Log.e(TAG, "callback not found for " + name + " message");
@@ -2807,56 +2822,63 @@ public class ConnectivityManager {
} }
} }
private CallbackHandler getHandler() { private void incCallbackHandlerRefCount() {
synchronized (sCallbacks) { synchronized(sCallbackRefCount) {
if (sCallbackHandler == null) { if (sCallbackRefCount.incrementAndGet() == 1) {
sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper()); // TODO: switch this to ConnectivityThread
HandlerThread callbackThread = new HandlerThread("ConnectivityManager");
callbackThread.start();
sCallbackHandler = new CallbackHandler(callbackThread.getLooper(),
sNetworkCallback, sCallbackRefCount, this);
} }
return sCallbackHandler;
} }
} }
static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>(); private void decCallbackHandlerRefCount() {
static CallbackHandler sCallbackHandler; synchronized(sCallbackRefCount) {
if (sCallbackRefCount.decrementAndGet() == 0) {
sCallbackHandler.obtainMessage(CALLBACK_EXIT).sendToTarget();
sCallbackHandler = null;
}
}
}
static final HashMap<NetworkRequest, NetworkCallback> sNetworkCallback =
new HashMap<NetworkRequest, NetworkCallback>();
static final AtomicInteger sCallbackRefCount = new AtomicInteger(0);
static CallbackHandler sCallbackHandler = null;
private final static int LISTEN = 1; private final static int LISTEN = 1;
private final static int REQUEST = 2; private final static int REQUEST = 2;
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
NetworkCallback callback, int timeoutMs, int action, int legacyType) { NetworkCallback networkCallback, int timeoutMs, int action,
return sendRequestForNetwork(need, callback, getHandler(), timeoutMs, action, legacyType); int legacyType) {
} if (networkCallback == null) {
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
NetworkCallback callback, Handler handler, int timeoutMs, int action, int legacyType) {
if (callback == null) {
throw new IllegalArgumentException("null NetworkCallback"); throw new IllegalArgumentException("null NetworkCallback");
} }
if (need == null && action != REQUEST) { if (need == null && action != REQUEST) {
throw new IllegalArgumentException("null NetworkCapabilities"); throw new IllegalArgumentException("null NetworkCapabilities");
} }
// TODO: throw an exception if callback.networkRequest is not null.
// http://b/20701525
final NetworkRequest request;
try { try {
synchronized(sCallbacks) { incCallbackHandlerRefCount();
Messenger messenger = new Messenger(handler); synchronized(sNetworkCallback) {
Binder binder = new Binder();
if (action == LISTEN) { if (action == LISTEN) {
request = mService.listenForNetwork(need, messenger, binder); networkCallback.networkRequest = mService.listenForNetwork(need,
new Messenger(sCallbackHandler), new Binder());
} else { } else {
request = mService.requestNetwork( networkCallback.networkRequest = mService.requestNetwork(need,
need, messenger, timeoutMs, binder, legacyType); new Messenger(sCallbackHandler), timeoutMs, new Binder(), legacyType);
} }
if (request != null) { if (networkCallback.networkRequest != null) {
sCallbacks.put(request, callback); sNetworkCallback.put(networkCallback.networkRequest, networkCallback);
} }
callback.networkRequest = request;
} }
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }
return request; if (networkCallback.networkRequest == null) decCallbackHandlerRefCount();
return networkCallback.networkRequest;
} }
/** /**

View File

@@ -2591,14 +2591,28 @@ public class ConnectivityService extends IConnectivityManager.Stub
"request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED); "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED);
} }
private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
nri.request.requestId) == null) {
handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_UNAVAIL);
}
}
private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
final NetworkRequestInfo nri = getNriForAppRequest( final NetworkRequestInfo nri = getNriForAppRequest(
request, callingUid, "release NetworkRequest"); request, callingUid, "release NetworkRequest");
if (nri == null) return; if (nri != null) {
handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_RELEASED);
}
}
if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request); private void handleRemoveNetworkRequest(final NetworkRequestInfo nri, final int whichCallback) {
final String logCallbackType = ConnectivityManager.getCallbackName(whichCallback);
if (VDBG || (DBG && nri.request.isRequest())) {
log("releasing " + nri.request + " (" + logCallbackType + ")");
}
nri.unlinkDeathRecipient(); nri.unlinkDeathRecipient();
mNetworkRequests.remove(request); mNetworkRequests.remove(nri.request);
synchronized (mUidToNetworkRequestCount) { synchronized (mUidToNetworkRequestCount) {
int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
if (requests < 1) { if (requests < 1) {
@@ -2692,7 +2706,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
} }
} }
callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0); callCallbackForRequest(nri, null, whichCallback, 0);
} }
@Override @Override
@@ -2929,6 +2943,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
handleRegisterNetworkRequestWithIntent(msg); handleRegisterNetworkRequestWithIntent(msg);
break; break;
} }
case EVENT_TIMEOUT_NETWORK_REQUEST: {
NetworkRequestInfo nri = (NetworkRequestInfo) msg.obj;
handleTimedOutNetworkRequest(nri);
break;
}
case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: { case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: {
handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1); handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1);
break; break;

View File

@@ -1077,7 +1077,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {
NETWORK_CAPABILITIES, NETWORK_CAPABILITIES,
LINK_PROPERTIES, LINK_PROPERTIES,
LOSING, LOSING,
LOST LOST,
UNAVAILABLE
} }
private static class CallbackInfo { private static class CallbackInfo {
@@ -1125,6 +1126,11 @@ public class ConnectivityServiceTest extends AndroidTestCase {
setLastCallback(CallbackState.AVAILABLE, network, null); setLastCallback(CallbackState.AVAILABLE, network, null);
} }
@Override
public void onUnavailable() {
setLastCallback(CallbackState.UNAVAILABLE, null, null);
}
@Override @Override
public void onLosing(Network network, int maxMsToLive) { public void onLosing(Network network, int maxMsToLive) {
setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */); setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
@@ -2236,6 +2242,79 @@ public class ConnectivityServiceTest extends AndroidTestCase {
mCm.unregisterNetworkCallback(defaultCallback); mCm.unregisterNetworkCallback(defaultCallback);
} }
/**
* Validate that a satisfied network request does not trigger onUnavailable() once the
* time-out period expires.
*/
@SmallTest
public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() {
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.requestNetwork(nr, networkCallback, 10);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
// pass timeout and validate that UNAVAILABLE is not called
try {
Thread.sleep(15);
} catch (InterruptedException e) {
}
networkCallback.assertNoCallback();
}
/**
* Validate that when a time-out is specified for a network request the onUnavailable()
* callback is called when time-out expires. Then validate that if network request is
* (somehow) satisfied - the callback isn't called later.
*/
@SmallTest
public void testTimedoutNetworkRequest() {
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.requestNetwork(nr, networkCallback, 10);
// pass timeout and validate that UNAVAILABLE is called
networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
// create a network satisfying request - validate that request not triggered
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
networkCallback.assertNoCallback();
}
/**
* Validate that when a network request is unregistered (cancelled) the time-out for that
* request doesn't trigger the onUnavailable() callback.
*/
@SmallTest
public void testTimedoutAfterUnregisteredNetworkRequest() {
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.requestNetwork(nr, networkCallback, 10);
// remove request
mCm.unregisterNetworkCallback(networkCallback);
// pass timeout and validate that no callbacks
// Note: doesn't validate that nothing called from CS since even if called the CM already
// unregisters the callback and won't pass it through!
try {
Thread.sleep(15);
} catch (InterruptedException e) {
}
networkCallback.assertNoCallback();
// create a network satisfying request - validate that request not triggered
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
networkCallback.assertNoCallback();
}
private static class TestKeepaliveCallback extends PacketKeepaliveCallback { private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };