Implemented requestNetwork with a PendingIntent.
ConnectivityManager.requestNetwork(NetworkRequest, PendingIntent)
was unhidden and implemented.
Added ConnectivityManager.removePendingIntentRequest(PendingIntent) as
the companion method.
Bug: 17356414
Change-Id: I656a1e149cc1292c443ebfe9e61ee3eb5a80f143
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a09c6c7..1c9f4c6 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2378,17 +2378,15 @@
/**
* The lookup key for a {@link Network} object included with the intent after
- * succesfully finding a network for the applications request. Retrieve it with
+ * successfully finding a network for the applications request. Retrieve it with
* {@link android.content.Intent#getParcelableExtra(String)}.
- * @hide
*/
public static final String EXTRA_NETWORK_REQUEST_NETWORK = "networkRequestNetwork";
/**
* The lookup key for a {@link NetworkRequest} object included with the intent after
- * succesfully finding a network for the applications request. Retrieve it with
+ * successfully finding a network for the applications request. Retrieve it with
* {@link android.content.Intent#getParcelableExtra(String)}.
- * @hide
*/
public static final String EXTRA_NETWORK_REQUEST_NETWORK_REQUEST =
"networkRequestNetworkRequest";
@@ -2397,7 +2395,7 @@
/**
* Request a network to satisfy a set of {@link NetworkCapabilities}.
*
- * This function behavies identically to the version that takes a NetworkCallback, but instead
+ * This function behaves identically to the version that takes a NetworkCallback, but instead
* of {@link NetworkCallback} a {@link PendingIntent} is used. This means
* the request may outlive the calling application and get called back when a suitable
* network is found.
@@ -2418,21 +2416,46 @@
* two Intents defined by {@link Intent#filterEquals}), then it will be removed and
* replaced by this one, effectively releasing the previous {@link NetworkRequest}.
* <p>
- * The request may be released normally by calling {@link #unregisterNetworkCallback}.
+ * The request may be released normally by calling
+ * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
*
* @param request {@link NetworkRequest} describing this request.
* @param operation Action to perform when the network is available (corresponds
* to the {@link NetworkCallback#onAvailable} call. Typically
- * comes from {@link PendingIntent#getBroadcast}.
- * @hide
+ * comes from {@link PendingIntent#getBroadcast}. Cannot be null.
*/
public void requestNetwork(NetworkRequest request, PendingIntent operation) {
+ checkPendingIntent(operation);
try {
mService.pendingRequestForNetwork(request.networkCapabilities, operation);
} catch (RemoteException e) {}
}
/**
+ * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)}
+ * <p>
+ * This method has the same behavior as {@link #unregisterNetworkCallback} with respect to
+ * releasing network resources and disconnecting.
+ *
+ * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+ * PendingIntent passed to
+ * {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the
+ * corresponding NetworkRequest you'd like to remove. Cannot be null.
+ */
+ public void releaseNetworkRequest(PendingIntent operation) {
+ checkPendingIntent(operation);
+ try {
+ mService.releasePendingNetworkRequest(operation);
+ } catch (RemoteException e) {}
+ }
+
+ private void checkPendingIntent(PendingIntent intent) {
+ if (intent == null) {
+ throw new IllegalArgumentException("PendingIntent cannot be null.");
+ }
+ }
+
+ /**
* Registers to receive notifications about all networks which satisfy the given
* {@link NetworkRequest}. The callbacks will continue to be called until
* either the application exits or {@link #unregisterNetworkCallback} is called
@@ -2448,7 +2471,7 @@
/**
* Unregisters callbacks about and possibly releases networks originating from
* {@link #requestNetwork} and {@link #registerNetworkCallback} calls. If the
- * given {@code NetworkCallback} had previosuly been used with {@code #requestNetwork},
+ * given {@code NetworkCallback} had previously been used with {@code #requestNetwork},
* any networks that had been connected to only to satisfy that request will be
* disconnected.
*
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index a983d88..a7bbc53 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -156,6 +156,8 @@
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation);
+ void releasePendingNetworkRequest(in PendingIntent operation);
+
NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, in IBinder binder);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index a2d246d..1d4d671 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -184,7 +184,8 @@
/**
* @hide
*/
-public class ConnectivityService extends IConnectivityManager.Stub {
+public class ConnectivityService extends IConnectivityManager.Stub
+ implements PendingIntent.OnFinished {
private static final String TAG = "ConnectivityService";
private static final boolean DBG = true;
@@ -382,6 +383,19 @@
*/
private static final int EVENT_SYSTEM_READY = 25;
+ /**
+ * used to add a network request with a pending intent
+ * includes a NetworkRequestInfo
+ */
+ private static final int EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT = 26;
+
+ /**
+ * used to remove a pending intent and its associated network request.
+ * arg1 = UID of caller
+ * obj = PendingIntent
+ */
+ private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;
+
/** Handler used for internal events. */
final private InternalHandler mHandler;
@@ -395,6 +409,7 @@
private String mNetTransitionWakeLockCausedBy = "";
private int mNetTransitionWakeLockSerialNumber;
private int mNetTransitionWakeLockTimeout;
+ private final PowerManager.WakeLock mPendingIntentWakeLock;
private InetAddress mDefaultDns;
@@ -649,6 +664,7 @@
mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_networkTransitionTimeout);
+ mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mNetTrackers = new NetworkStateTracker[
ConnectivityManager.MAX_NETWORK_TYPE+1];
@@ -2131,11 +2147,40 @@
}
}
+ // If this method proves to be too slow then we can maintain a separate
+ // pendingIntent => NetworkRequestInfo map.
+ // This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo.
+ private NetworkRequestInfo findExistingNetworkRequestInfo(PendingIntent pendingIntent) {
+ Intent intent = pendingIntent.getIntent();
+ for (Map.Entry<NetworkRequest, NetworkRequestInfo> entry : mNetworkRequests.entrySet()) {
+ PendingIntent existingPendingIntent = entry.getValue().mPendingIntent;
+ if (existingPendingIntent != null &&
+ existingPendingIntent.getIntent().filterEquals(intent)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ private void handleRegisterNetworkRequestWithIntent(Message msg) {
+ final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
+
+ NetworkRequestInfo existingRequest = findExistingNetworkRequestInfo(nri.mPendingIntent);
+ if (existingRequest != null) { // remove the existing request.
+ if (DBG) log("Replacing " + existingRequest.request + " with "
+ + nri.request + " because their intents matched.");
+ handleReleaseNetworkRequest(existingRequest.request, getCallingUid());
+ }
+ handleRegisterNetworkRequest(msg);
+ }
+
private void handleRegisterNetworkRequest(Message msg) {
final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
final NetworkCapabilities newCap = nri.request.networkCapabilities;
int score = 0;
+ mNetworkRequests.put(nri.request, nri);
+
// Check for the best currently alive network that satisfies this request
NetworkAgentInfo bestNetwork = null;
for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
@@ -2173,7 +2218,7 @@
mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
}
}
- mNetworkRequests.put(nri.request, nri);
+
if (nri.isRequest) {
if (DBG) log("sending new NetworkRequest to factories");
for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
@@ -2183,6 +2228,14 @@
}
}
+ private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent,
+ int callingUid) {
+ NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
+ if (nri != null) {
+ handleReleaseNetworkRequest(nri.request, callingUid);
+ }
+ }
+
private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
NetworkRequestInfo nri = mNetworkRequests.get(request);
if (nri != null) {
@@ -2218,11 +2271,11 @@
}
}
- // Maintain the illusion. When this request arrived, we might have preteneded
+ // 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
- // phatom disconnect for this type.
+ // phantom disconnect for this type.
NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
if (nai != null) {
mNetworkForRequestId.remove(nri.request.requestId);
@@ -2253,7 +2306,6 @@
@Override
public void handleMessage(Message msg) {
- NetworkInfo info;
switch (msg.what) {
case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK:
case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
@@ -2334,6 +2386,14 @@
handleRegisterNetworkRequest(msg);
break;
}
+ case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT: {
+ handleRegisterNetworkRequestWithIntent(msg);
+ break;
+ }
+ case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: {
+ handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1);
+ break;
+ }
case EVENT_RELEASE_NETWORK_REQUEST: {
handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
break;
@@ -3347,12 +3407,23 @@
static final boolean LISTEN = false;
final NetworkRequest request;
- IBinder mBinder;
+ final PendingIntent mPendingIntent;
+ private final IBinder mBinder;
final int mPid;
final int mUid;
final Messenger messenger;
final boolean isRequest;
+ NetworkRequestInfo(NetworkRequest r, PendingIntent pi, boolean isRequest) {
+ request = r;
+ mPendingIntent = pi;
+ messenger = null;
+ mBinder = null;
+ mPid = getCallingPid();
+ mUid = getCallingUid();
+ this.isRequest = isRequest;
+ }
+
NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
super();
messenger = m;
@@ -3361,6 +3432,7 @@
mPid = getCallingPid();
mUid = getCallingUid();
this.isRequest = isRequest;
+ mPendingIntent = null;
try {
mBinder.linkToDeath(this, 0);
@@ -3370,7 +3442,9 @@
}
void unlinkDeathRecipient() {
- mBinder.unlinkToDeath(this, 0);
+ if (mBinder != null) {
+ mBinder.unlinkToDeath(this, 0);
+ }
}
public void binderDied() {
@@ -3381,40 +3455,22 @@
public String toString() {
return (isRequest ? "Request" : "Listen") + " from uid/pid:" + mUid + "/" +
- mPid + " for " + request;
+ mPid + " for " + request +
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
}
}
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
- if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- == false) {
- enforceConnectivityInternalPermission();
- } else {
- enforceChangePermission();
- }
-
networkCapabilities = new NetworkCapabilities(networkCapabilities);
-
- // if UID is restricted, don't allow them to bring up metered APNs
- if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
- == false) {
- final int uidRules;
- final int uid = Binder.getCallingUid();
- synchronized(mRulesLock) {
- uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
- }
- if ((uidRules & RULE_REJECT_METERED) != 0) {
- // we could silently fail or we can filter the available nets to only give
- // them those they have access to. Chose the more useful
- networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- }
- }
+ enforceNetworkRequestPermissions(networkCapabilities);
+ enforceMeteredApnPolicy(networkCapabilities);
if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
throw new IllegalArgumentException("Bad timeout specified");
}
+
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId());
if (DBG) log("requestNetwork for " + networkRequest);
@@ -3429,11 +3485,54 @@
return networkRequest;
}
+ private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities) {
+ if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ == false) {
+ enforceConnectivityInternalPermission();
+ } else {
+ enforceChangePermission();
+ }
+ }
+
+ private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
+ // if UID is restricted, don't allow them to bring up metered APNs
+ if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ == false) {
+ final int uidRules;
+ final int uid = Binder.getCallingUid();
+ synchronized(mRulesLock) {
+ uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ }
+ if ((uidRules & RULE_REJECT_METERED) != 0) {
+ // we could silently fail or we can filter the available nets to only give
+ // them those they have access to. Chose the more useful
+ networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ }
+ }
+ }
+
@Override
public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities,
PendingIntent operation) {
- // TODO
- return null;
+ checkNotNull(operation, "PendingIntent cannot be null.");
+ networkCapabilities = new NetworkCapabilities(networkCapabilities);
+ enforceNetworkRequestPermissions(networkCapabilities);
+ enforceMeteredApnPolicy(networkCapabilities);
+
+ NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
+ nextNetworkRequestId());
+ if (DBG) log("pendingRequest for " + networkRequest + " to trigger " + operation);
+ NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
+ NetworkRequestInfo.REQUEST);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
+ nri));
+ return networkRequest;
+ }
+
+ @Override
+ public void releasePendingNetworkRequest(PendingIntent operation) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT,
+ getCallingUid(), 0, operation));
}
@Override
@@ -3727,6 +3826,39 @@
}
}
+ private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
+ int notificationType) {
+ if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE) {
+ Intent intent = new Intent();
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST_NETWORK, nri.request);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST_NETWORK_REQUEST,
+ networkAgent.network);
+ sendIntent(nri.mPendingIntent, intent);
+ }
+ // else not handled
+ }
+
+ private void sendIntent(PendingIntent pendingIntent, Intent intent) {
+ mPendingIntentWakeLock.acquire();
+ try {
+ if (DBG) log("Sending " + pendingIntent);
+ pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */);
+ } catch (PendingIntent.CanceledException e) {
+ if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
+ mPendingIntentWakeLock.release();
+ releasePendingNetworkRequest(pendingIntent);
+ }
+ // ...otherwise, mPendingIntentWakeLock.release() gets called by onSendFinished()
+ }
+
+ @Override
+ public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+ String resultData, Bundle resultExtras) {
+ if (DBG) log("Finished sending " + pendingIntent);
+ mPendingIntentWakeLock.release();
+ releasePendingNetworkRequest(pendingIntent);
+ }
+
private void callCallbackForRequest(NetworkRequestInfo nri,
NetworkAgentInfo networkAgent, int notificationType) {
if (nri.messenger == null) return; // Default request has no msgr
@@ -4137,7 +4269,11 @@
// } else if (nai.networkMonitor.isEvaluating()) {
// notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType);
// }
- callCallbackForRequest(nri, nai, notifyType);
+ if (nri.mPendingIntent == null) {
+ callCallbackForRequest(nri, nai, notifyType);
+ } else {
+ sendPendingIntentForRequest(nri, nai, notifyType);
+ }
}
private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
@@ -4196,7 +4332,11 @@
NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (VDBG) log(" sending notification for " + nr);
- callCallbackForRequest(nri, networkAgent, notifyType);
+ if (nri.mPendingIntent == null) {
+ callCallbackForRequest(nri, networkAgent, notifyType);
+ } else {
+ sendPendingIntentForRequest(nri, networkAgent, notifyType);
+ }
}
}