am e3bcde94: Merge "Reduce CONNECTIVITY_CHANGE bcasts" into mnc-dev
* commit 'e3bcde94caf8440d5e3958a340b3d276d03f7691':
Reduce CONNECTIVITY_CHANGE bcasts
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 8e55736..ec0cc6d 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1181,6 +1181,142 @@
return true;
}
+ /** @hide */
+ public static class PacketKeepaliveCallback {
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(int error) {}
+ }
+
+ /**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link ConnectivityManager.PacketKeepalive} object, such as {@link #startNattKeepalive},
+ * passing in a non-null callback. If the callback is successfully started, the callback's
+ * {@code onStarted} method will be called. If an error occurs, {@code onError} will be called,
+ * specifying one of the {@code ERROR_*} constants in this class.
+ *
+ * To stop an existing keepalive, call {@link stop}. The system will call {@code onStopped} if
+ * the operation was successfull or {@code onError} if an error occurred.
+ *
+ * @hide
+ */
+ public class PacketKeepalive {
+
+ private static final String TAG = "PacketKeepalive";
+
+ /** @hide */
+ public static final int SUCCESS = 0;
+
+ /** @hide */
+ public static final int NO_KEEPALIVE = -1;
+
+ /** @hide */
+ public static final int BINDER_DIED = -10;
+
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = -20;
+ /** The specified IP addresses are invalid. For example, the specified source IP address is
+ * not configured on the specified {@code Network}. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+ /** The requested port is invalid. */
+ public static final int ERROR_INVALID_PORT = -22;
+ /** The packet length is invalid (e.g., too long). */
+ public static final int ERROR_INVALID_LENGTH = -23;
+ /** The packet transmission interval is invalid (e.g., too short). */
+ public static final int ERROR_INVALID_INTERVAL = -24;
+
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+
+ public static final int NATT_PORT = 4500;
+
+ private final Network mNetwork;
+ private final PacketKeepaliveCallback mCallback;
+ private final Looper mLooper;
+ private final Messenger mMessenger;
+
+ private volatile Integer mSlot;
+
+ void stopLooper() {
+ mLooper.quit();
+ }
+
+ public void stop() {
+ try {
+ mService.stopKeepalive(mNetwork, mSlot);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ stopLooper();
+ }
+ }
+
+ private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
+ checkNotNull(network, "network cannot be null");
+ checkNotNull(callback, "callback cannot be null");
+ mNetwork = network;
+ mCallback = callback;
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ mLooper = thread.getLooper();
+ mMessenger = new Messenger(new Handler(mLooper) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case NetworkAgent.EVENT_PACKET_KEEPALIVE:
+ int error = message.arg2;
+ try {
+ if (error == SUCCESS) {
+ if (mSlot == null) {
+ mSlot = message.arg1;
+ mCallback.onStarted();
+ } else {
+ mSlot = null;
+ stopLooper();
+ mCallback.onStopped();
+ }
+ } else {
+ stopLooper();
+ mCallback.onError(error);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in keepalive callback(" + error + ")", e);
+ }
+ break;
+ default:
+ Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+ break;
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Starts an IPsec NAT-T keepalive packet with the specified parameters.
+ *
+ * @hide
+ */
+ public PacketKeepalive startNattKeepalive(
+ Network network, int intervalSeconds, PacketKeepaliveCallback callback,
+ InetAddress srcAddr, int srcPort, InetAddress dstAddr) {
+ final PacketKeepalive k = new PacketKeepalive(network, callback);
+ try {
+ mService.startNattKeepalive(network, intervalSeconds, k.mMessenger, new Binder(),
+ srcAddr.getHostAddress(), srcPort, dstAddr.getHostAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting packet keepalive: ", e);
+ k.stopLooper();
+ return null;
+ }
+ return k;
+ }
+
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 46c28a6..d4dd669 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -160,4 +160,9 @@
boolean setUnderlyingNetworksForVpn(in Network[] networks);
void factoryReset();
+
+ void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
+ in IBinder binder, String srcAddr, int srcPort, String dstAddr);
+
+ void stopKeepalive(in Network network, int slot);
}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 808a882..20c2168 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -17,6 +17,7 @@
package android.net;
import android.content.Context;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -25,6 +26,7 @@
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
+import android.net.ConnectivityManager.PacketKeepalive;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -143,17 +145,60 @@
*/
public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
- /** Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
* the underlying network connection for updated bandwidth information.
*/
public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
/**
+ * Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent
+ * periodically on the given interval.
+ *
+ * arg1 = the slot number of the keepalive to start
+ * arg2 = interval in seconds
+ * obj = KeepalivePacketData object describing the data to be sent
+ *
+ * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ */
+ public static final int CMD_START_PACKET_KEEPALIVE = BASE + 11;
+
+ /**
+ * Requests that the specified keepalive packet be stopped.
+ *
+ * arg1 = slot number of the keepalive to stop.
+ *
+ * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ */
+ public static final int CMD_STOP_PACKET_KEEPALIVE = BASE + 12;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to provide status on a packet keepalive
+ * request. This may either be the reply to a CMD_START_PACKET_KEEPALIVE, or an asynchronous
+ * error notification.
+ *
+ * This is also sent by KeepaliveTracker to the app's ConnectivityManager.PacketKeepalive to
+ * so that the app's PacketKeepaliveCallback methods can be called.
+ *
+ * arg1 = slot number of the keepalive
+ * arg2 = error code
+ */
+ public static final int EVENT_PACKET_KEEPALIVE = BASE + 13;
+
+ /**
+ * Sent by ConnectivityService to inform this network transport of signal strength thresholds
+ * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+ *
+ * obj = int[] describing signal strength thresholds.
+ */
+ public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;
+
+ /**
* Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
* automatically reconnecting to this network (e.g. via autojoin). Happens
* when user selects "No" option on the "Stay connected?" dialog box.
*/
- public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 11;
+ public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
@@ -249,6 +294,27 @@
saveAcceptUnvalidated(msg.arg1 != 0);
break;
}
+ case CMD_START_PACKET_KEEPALIVE: {
+ startPacketKeepalive(msg);
+ break;
+ }
+ case CMD_STOP_PACKET_KEEPALIVE: {
+ stopPacketKeepalive(msg);
+ break;
+ }
+
+ case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
+ ArrayList<Integer> thresholds =
+ ((Bundle) msg.obj).getIntegerArrayList("thresholds");
+ // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
+ // rather than convert to int[].
+ int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
+ for (int i = 0; i < intThresholds.length; i++) {
+ intThresholds[i] = thresholds.get(i);
+ }
+ setSignalStrengthThresholds(intThresholds);
+ break;
+ }
case CMD_PREVENT_AUTOMATIC_RECONNECT: {
preventAutomaticReconnect();
break;
@@ -257,13 +323,27 @@
}
private void queueOrSendMessage(int what, Object obj) {
+ queueOrSendMessage(what, 0, 0, obj);
+ }
+
+ private void queueOrSendMessage(int what, int arg1, int arg2) {
+ queueOrSendMessage(what, arg1, arg2, null);
+ }
+
+ private void queueOrSendMessage(int what, int arg1, int arg2, Object obj) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = arg1;
+ msg.arg2 = arg2;
+ msg.obj = obj;
+ queueOrSendMessage(msg);
+ }
+
+ private void queueOrSendMessage(Message msg) {
synchronized (mPreConnectedQueue) {
if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(what, obj);
+ mAsyncChannel.sendMessage(msg);
} else {
- Message msg = Message.obtain();
- msg.what = what;
- msg.obj = obj;
mPreConnectedQueue.add(msg);
}
}
@@ -378,6 +458,34 @@
}
/**
+ * Requests that the network hardware send the specified packet at the specified interval.
+ */
+ protected void startPacketKeepalive(Message msg) {
+ onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ }
+
+ /**
+ * Requests that the network hardware send the specified packet at the specified interval.
+ */
+ protected void stopPacketKeepalive(Message msg) {
+ onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ }
+
+ /**
+ * Called by the network when a packet keepalive event occurs.
+ */
+ public void onPacketKeepaliveEvent(int slot, int reason) {
+ queueOrSendMessage(EVENT_PACKET_KEEPALIVE, slot, reason);
+ }
+
+ /**
+ * Called by ConnectivityService to inform this network transport of signal strength thresholds
+ * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+ */
+ protected void setSignalStrengthThresholds(int[] thresholds) {
+ }
+
+ /**
* Called when the user asks to not stay connected to this network because it was found to not
* provide Internet access. Usually followed by call to {@code unwanted}. The transport is
* responsible for making sure the device does not automatically reconnect to the same network
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index d0e0cbe..3bd12c0 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -48,6 +48,7 @@
mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
mNetworkSpecifier = nc.mNetworkSpecifier;
+ mSignalStrength = nc.mSignalStrength;
}
}
@@ -60,6 +61,7 @@
mNetworkCapabilities = mTransportTypes = 0;
mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = 0;
mNetworkSpecifier = null;
+ mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
}
/**
@@ -184,6 +186,28 @@
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_CAPTIVE_PORTAL;
/**
+ * Network capabilities that are expected to be mutable, i.e., can change while a particular
+ * network is connected.
+ */
+ private static final long MUTABLE_CAPABILITIES =
+ // TRUSTED can change when user explicitly connects to an untrusted network in Settings.
+ // http://b/18206275
+ (1 << NET_CAPABILITY_TRUSTED) |
+ (1 << NET_CAPABILITY_VALIDATED) |
+ (1 << NET_CAPABILITY_CAPTIVE_PORTAL);
+
+ /**
+ * Network capabilities that are not allowed in NetworkRequests. This exists because the
+ * NetworkFactory / NetworkAgent model does not deal well with the situation where a
+ * capability's presence cannot be known in advance. If such a capability is requested, then we
+ * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then
+ * get immediately torn down because they do not have the requested capability.
+ */
+ private static final long NON_REQUESTABLE_CAPABILITIES =
+ (1 << NET_CAPABILITY_VALIDATED) |
+ (1 << NET_CAPABILITY_CAPTIVE_PORTAL);
+
+ /**
* Capabilities that are set by default when the object is constructed.
*/
private static final long DEFAULT_CAPABILITIES =
@@ -278,8 +302,31 @@
this.mNetworkCapabilities |= nc.mNetworkCapabilities;
}
- private boolean satisfiedByNetCapabilities(NetworkCapabilities nc) {
- return ((nc.mNetworkCapabilities & this.mNetworkCapabilities) == this.mNetworkCapabilities);
+ /**
+ * Convenience function that returns a human-readable description of the first mutable
+ * capability we find. Used to present an error message to apps that request mutable
+ * capabilities.
+ *
+ * @hide
+ */
+ public String describeFirstNonRequestableCapability() {
+ if (hasCapability(NET_CAPABILITY_VALIDATED)) return "NET_CAPABILITY_VALIDATED";
+ if (hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return "NET_CAPABILITY_CAPTIVE_PORTAL";
+ // This cannot happen unless the preceding checks are incomplete.
+ if ((mNetworkCapabilities & NON_REQUESTABLE_CAPABILITIES) != 0) {
+ return "unknown non-requestable capabilities " + Long.toHexString(mNetworkCapabilities);
+ }
+ if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth";
+ if (hasSignalStrength()) return "signalStrength";
+ return null;
+ }
+
+ private boolean satisfiedByNetCapabilities(NetworkCapabilities nc, boolean onlyImmutable) {
+ long networkCapabilities = this.mNetworkCapabilities;
+ if (onlyImmutable) {
+ networkCapabilities = networkCapabilities & ~MUTABLE_CAPABILITIES;
+ }
+ return ((nc.mNetworkCapabilities & networkCapabilities) == networkCapabilities);
}
/** @hide */
@@ -287,6 +334,11 @@
return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
}
+ private boolean equalsNetCapabilitiesImmutable(NetworkCapabilities that) {
+ return ((this.mNetworkCapabilities & ~MUTABLE_CAPABILITIES) ==
+ (that.mNetworkCapabilities & ~MUTABLE_CAPABILITIES));
+ }
+
/**
* Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are
* typically provided by restricted networks.
@@ -555,26 +607,130 @@
}
/**
+ * Magic value that indicates no signal strength provided. A request specifying this value is
+ * always satisfied.
+ *
+ * @hide
+ */
+ public static final int SIGNAL_STRENGTH_UNSPECIFIED = Integer.MIN_VALUE;
+
+ /**
+ * Signal strength. This is a signed integer, and higher values indicate better signal.
+ * The exact units are bearer-dependent. For example, Wi-Fi uses RSSI.
+ */
+ private int mSignalStrength;
+
+ /**
+ * Sets the signal strength. This is a signed integer, with higher values indicating a stronger
+ * signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same RSSI units
+ * reported by WifiManager.
+ * <p>
+ * Note that when used to register a network callback, this specifies the minimum acceptable
+ * signal strength. When received as the state of an existing network it specifies the current
+ * value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has no
+ * effect when requesting a callback.
+ *
+ * @param signalStrength the bearer-specific signal strength.
+ * @hide
+ */
+ public void setSignalStrength(int signalStrength) {
+ mSignalStrength = signalStrength;
+ }
+
+ /**
+ * Returns {@code true} if this object specifies a signal strength.
+ *
+ * @hide
+ */
+ public boolean hasSignalStrength() {
+ return mSignalStrength > SIGNAL_STRENGTH_UNSPECIFIED;
+ }
+
+ /**
+ * Retrieves the signal strength.
+ *
+ * @return The bearer-specific signal strength.
+ * @hide
+ */
+ public int getSignalStrength() {
+ return mSignalStrength;
+ }
+
+ private void combineSignalStrength(NetworkCapabilities nc) {
+ this.mSignalStrength = Math.max(this.mSignalStrength, nc.mSignalStrength);
+ }
+
+ private boolean satisfiedBySignalStrength(NetworkCapabilities nc) {
+ return this.mSignalStrength <= nc.mSignalStrength;
+ }
+
+ private boolean equalsSignalStrength(NetworkCapabilities nc) {
+ return this.mSignalStrength == nc.mSignalStrength;
+ }
+
+ /**
* Combine a set of Capabilities to this one. Useful for coming up with the complete set
- * {@hide}
+ * @hide
*/
public void combineCapabilities(NetworkCapabilities nc) {
combineNetCapabilities(nc);
combineTransportTypes(nc);
combineLinkBandwidths(nc);
combineSpecifiers(nc);
+ combineSignalStrength(nc);
}
/**
- * Check if our requirements are satisfied by the given Capabilities.
- * {@hide}
+ * Check if our requirements are satisfied by the given {@code NetworkCapabilities}.
+ *
+ * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements.
+ * @param onlyImmutable if {@code true}, do not consider mutable requirements such as link
+ * bandwidth, signal strength, or validation / captive portal status.
+ *
+ * @hide
+ */
+ private boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc, boolean onlyImmutable) {
+ return (nc != null &&
+ satisfiedByNetCapabilities(nc, onlyImmutable) &&
+ satisfiedByTransportTypes(nc) &&
+ (onlyImmutable || satisfiedByLinkBandwidths(nc)) &&
+ satisfiedBySpecifier(nc) &&
+ (onlyImmutable || satisfiedBySignalStrength(nc)));
+ }
+
+ /**
+ * Check if our requirements are satisfied by the given {@code NetworkCapabilities}.
+ *
+ * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements.
+ *
+ * @hide
*/
public boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc) {
- return (nc != null &&
- satisfiedByNetCapabilities(nc) &&
- satisfiedByTransportTypes(nc) &&
- satisfiedByLinkBandwidths(nc) &&
- satisfiedBySpecifier(nc));
+ return satisfiedByNetworkCapabilities(nc, false);
+ }
+
+ /**
+ * Check if our immutable requirements are satisfied by the given {@code NetworkCapabilities}.
+ *
+ * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements.
+ *
+ * @hide
+ */
+ public boolean satisfiedByImmutableNetworkCapabilities(NetworkCapabilities nc) {
+ return satisfiedByNetworkCapabilities(nc, true);
+ }
+
+ /**
+ * Checks that our immutable capabilities are the same as those of the given
+ * {@code NetworkCapabilities}.
+ *
+ * @hide
+ */
+ public boolean equalImmutableCapabilities(NetworkCapabilities nc) {
+ if (nc == null) return false;
+ return (equalsNetCapabilitiesImmutable(nc) &&
+ equalsTransportTypes(nc) &&
+ equalsSpecifier(nc));
}
@Override
@@ -584,6 +740,7 @@
return (equalsNetCapabilities(that) &&
equalsTransportTypes(that) &&
equalsLinkBandwidths(that) &&
+ equalsSignalStrength(that) &&
equalsSpecifier(that));
}
@@ -595,7 +752,8 @@
((int)(mTransportTypes >> 32) * 7) +
(mLinkUpBandwidthKbps * 11) +
(mLinkDownBandwidthKbps * 13) +
- (TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17));
+ (TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17) +
+ (mSignalStrength * 19));
}
@Override
@@ -609,7 +767,9 @@
dest.writeInt(mLinkUpBandwidthKbps);
dest.writeInt(mLinkDownBandwidthKbps);
dest.writeString(mNetworkSpecifier);
+ dest.writeInt(mSignalStrength);
}
+
public static final Creator<NetworkCapabilities> CREATOR =
new Creator<NetworkCapabilities>() {
@Override
@@ -621,6 +781,7 @@
netCap.mLinkUpBandwidthKbps = in.readInt();
netCap.mLinkDownBandwidthKbps = in.readInt();
netCap.mNetworkSpecifier = in.readString();
+ netCap.mSignalStrength = in.readInt();
return netCap;
}
@Override
@@ -678,6 +839,8 @@
String specifier = (mNetworkSpecifier == null ?
"" : " Specifier: <" + mNetworkSpecifier + ">");
- return "[" + transports + capabilities + upBand + dnBand + specifier + "]";
+ String signalStrength = (hasSignalStrength() ? " SignalStrength: " + mSignalStrength : "");
+
+ return "[" + transports + capabilities + upBand + dnBand + specifier + signalStrength + "]";
}
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index e184ec4..7da4818 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -191,6 +191,24 @@
mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
return this;
}
+
+ /**
+ * Sets the signal strength. This is a signed integer, with higher values indicating a
+ * stronger signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same
+ * RSSI units reported by WifiManager.
+ * <p>
+ * Note that when used to register a network callback, this specifies the minimum acceptable
+ * signal strength. When received as the state of an existing network it specifies the
+ * current value. A value of {@code SIGNAL_STRENGTH_UNSPECIFIED} means no value when
+ * received and has no effect when requesting a callback.
+ *
+ * @param signalStrength the bearer-specific signal strength.
+ * @hide
+ */
+ public Builder setSignalStrength(int signalStrength) {
+ mNetworkCapabilities.setSignalStrength(signalStrength);
+ return this;
+ }
}
// implement the Parcelable interface
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4919bed..e19447d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -47,6 +47,7 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.PacketKeepalive;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
@@ -117,6 +118,7 @@
import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
@@ -148,6 +150,8 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.SortedSet;
+import java.util.TreeSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -399,6 +403,8 @@
TelephonyManager mTelephonyManager;
+ private KeepaliveTracker mKeepaliveTracker;
+
// sequence number for Networks; keep in sync with system/netd/NetworkController.cpp
private final static int MIN_NET_ID = 100; // some reserved marks
private final static int MAX_NET_ID = 65535;
@@ -764,6 +770,8 @@
mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+ mKeepaliveTracker = new KeepaliveTracker(mHandler);
}
private NetworkRequest createInternetRequestForTransport(int transportType) {
@@ -1450,6 +1458,10 @@
"ConnectivityService");
}
+ private void enforceKeepalivePermission() {
+ mContext.enforceCallingPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
+ }
+
public void sendConnectedBroadcast(NetworkInfo info) {
enforceConnectivityInternalPermission();
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
@@ -1841,10 +1853,13 @@
pw.println(", last requested never");
}
}
- pw.println();
+ pw.println();
mTethering.dump(fd, pw, args);
+ pw.println();
+ mKeepaliveTracker.dump(pw);
+
if (mInetLog != null && mInetLog.size() > 0) {
pw.println();
pw.println("Inet condition reports:");
@@ -1922,7 +1937,12 @@
(NetworkCapabilities)msg.obj;
if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
- Slog.wtf(TAG, "BUG: " + nai + " has stateful capability.");
+ Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
+ }
+ if (nai.created && !nai.networkCapabilities.equalImmutableCapabilities(
+ networkCapabilities)) {
+ Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
+ + nai.networkCapabilities + " -> " + networkCapabilities);
}
updateCapabilities(nai, networkCapabilities);
}
@@ -2006,6 +2026,15 @@
nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
break;
}
+ case NetworkAgent.EVENT_PACKET_KEEPALIVE: {
+ NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+ if (nai == null) {
+ loge("EVENT_PACKET_KEEPALIVE from unknown NetworkAgent");
+ break;
+ }
+ mKeepaliveTracker.handleEventPacketKeepalive(nai, msg);
+ break;
+ }
case NetworkMonitor.EVENT_NETWORK_TESTED: {
NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
if (isLiveNetworkAgent(nai, "EVENT_NETWORK_TESTED")) {
@@ -2148,6 +2177,8 @@
// sending all CALLBACK_LOST messages (for requests, not listens) at the end
// of rematchAllNetworksAndRequests
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
+ mKeepaliveTracker.handleStopAllKeepalives(nai,
+ ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
mNetworkAgentInfos.remove(msg.replyTo);
updateClat(null, nai.linkProperties, nai);
@@ -2226,6 +2257,13 @@
private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
mNetworkRequests.put(nri.request, nri);
mNetworkRequestInfoLogs.log("REGISTER " + nri);
+ if (!nri.isRequest) {
+ for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
+ if (network.satisfiesImmutableCapabilitiesOf(nri.request)) {
+ updateSignalStrengthThresholds(network);
+ }
+ }
+ }
rematchAllNetworksAndRequests(null, 0);
if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) {
sendUpdatedScoreToFactories(nri.request, 0);
@@ -2339,6 +2377,9 @@
// if this listen request applies and remove it.
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
nai.networkRequests.remove(nri.request.requestId);
+ if (nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
+ updateSignalStrengthThresholds(nai);
+ }
}
}
callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED);
@@ -2505,6 +2546,19 @@
handleMobileDataAlwaysOn();
break;
}
+ // Sent by KeepaliveTracker to process an app request on the state machine thread.
+ case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
+ mKeepaliveTracker.handleStartKeepalive(msg);
+ break;
+ }
+ // Sent by KeepaliveTracker to process an app request on the state machine thread.
+ case NetworkAgent.CMD_STOP_PACKET_KEEPALIVE: {
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj);
+ int slot = msg.arg1;
+ int reason = msg.arg2;
+ mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
+ break;
+ }
case EVENT_SYSTEM_READY: {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
nai.networkMonitor.systemReady = true;
@@ -3554,15 +3608,32 @@
}
}
- private void ensureImmutableCapabilities(NetworkCapabilities networkCapabilities) {
- if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
- throw new IllegalArgumentException(
- "Cannot request network with NET_CAPABILITY_VALIDATED");
+ private void ensureRequestableCapabilities(NetworkCapabilities networkCapabilities) {
+ final String badCapability = networkCapabilities.describeFirstNonRequestableCapability();
+ if (badCapability != null) {
+ throw new IllegalArgumentException("Cannot request network with " + badCapability);
}
- if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
- throw new IllegalArgumentException(
- "Cannot request network with NET_CAPABILITY_CAPTIVE_PORTAL");
+ }
+
+ private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) {
+ final SortedSet<Integer> thresholds = new TreeSet();
+ synchronized (nai) {
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.request.networkCapabilities.hasSignalStrength() &&
+ nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
+ thresholds.add(nri.request.networkCapabilities.getSignalStrength());
+ }
+ }
}
+ return new ArrayList<Integer>(thresholds);
+ }
+
+ private void updateSignalStrengthThresholds(NetworkAgentInfo nai) {
+ Bundle thresholds = new Bundle();
+ thresholds.putIntegerArrayList("thresholds", getSignalStrengthThresholds(nai));
+ nai.asyncChannel.sendMessage(
+ android.net.NetworkAgent.CMD_SET_SIGNAL_STRENGTH_THRESHOLDS,
+ 0, 0, thresholds);
}
@Override
@@ -3571,7 +3642,7 @@
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities);
enforceMeteredApnPolicy(networkCapabilities);
- ensureImmutableCapabilities(networkCapabilities);
+ ensureRequestableCapabilities(networkCapabilities);
if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
throw new IllegalArgumentException("Bad timeout specified");
@@ -3640,7 +3711,7 @@
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities);
enforceMeteredApnPolicy(networkCapabilities);
- ensureImmutableCapabilities(networkCapabilities);
+ ensureRequestableCapabilities(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId());
@@ -3866,6 +3937,8 @@
notifyIfacesChanged();
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
+
+ mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
}
private void updateClat(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) {
@@ -4532,6 +4605,15 @@
// TODO: support proxy per network.
}
+ // Whether a particular NetworkRequest listen should cause signal strength thresholds to
+ // be communicated to a particular NetworkAgent depends only on the network's immutable,
+ // capabilities, so it only needs to be done once on initial connect, not every time the
+ // network's capabilities change. Note that we do this before rematching the network,
+ // so we could decide to tear it down immediately afterwards. That's fine though - on
+ // disconnection NetworkAgents should stop any signal strength monitoring they have been
+ // doing.
+ updateSignalStrengthThresholds(networkAgent);
+
// Consider network even though it is not yet validated.
rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP);
@@ -4711,6 +4793,22 @@
}
@Override
+ public void startNattKeepalive(Network network, int intervalSeconds, Messenger messenger,
+ IBinder binder, String srcAddr, int srcPort, String dstAddr) {
+ enforceKeepalivePermission();
+ mKeepaliveTracker.startNattKeepalive(
+ getNetworkAgentInfoForNetwork(network),
+ intervalSeconds, messenger, binder,
+ srcAddr, srcPort, dstAddr, ConnectivityManager.PacketKeepalive.NATT_PORT);
+ }
+
+ @Override
+ public void stopKeepalive(Network network, int slot) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network));
+ }
+
+ @Override
public void factoryReset() {
enforceConnectivityInternalPermission();
diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
new file mode 100644
index 0000000..64b9399
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.system.OsConstants;
+import android.net.ConnectivityManager;
+import android.net.NetworkUtils;
+import android.net.util.IpUtils;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static android.net.ConnectivityManager.PacketKeepalive.*;
+
+/**
+ * Represents the actual packets that are sent by the
+ * {@link android.net.ConnectivityManager.PacketKeepalive} API.
+ *
+ * @hide
+ */
+public class KeepalivePacketData {
+ /** Protocol of the packet to send; one of the OsConstants.ETH_P_* values. */
+ public final int protocol;
+
+ /** Source IP address */
+ public final InetAddress srcAddress;
+
+ /** Destination IP address */
+ public final InetAddress dstAddress;
+
+ /** Source port */
+ public final int srcPort;
+
+ /** Destination port */
+ public final int dstPort;
+
+ /** Destination MAC address. Can change if routing changes. */
+ public byte[] dstMac;
+
+ /** Packet data. A raw byte string of packet data, not including the link-layer header. */
+ public final byte[] data;
+
+ private static final int IPV4_HEADER_LENGTH = 20;
+ private static final int UDP_HEADER_LENGTH = 8;
+
+ protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
+ InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
+ this.srcAddress = srcAddress;
+ this.dstAddress = dstAddress;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.data = data;
+
+ // Check we have two IP addresses of the same family.
+ if (srcAddress == null || dstAddress == null ||
+ !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) {
+ }
+
+ // Set the protocol.
+ if (this.dstAddress instanceof Inet4Address) {
+ this.protocol = OsConstants.ETH_P_IP;
+ } else if (this.dstAddress instanceof Inet6Address) {
+ this.protocol = OsConstants.ETH_P_IPV6;
+ } else {
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ // Check the ports.
+ if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) {
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
+ }
+ }
+
+ public static class InvalidPacketException extends Exception {
+ final public int error;
+ public InvalidPacketException(int error) {
+ this.error = error;
+ }
+ }
+
+ /**
+ * Creates an IPsec NAT-T keepalive packet with the specified parameters.
+ */
+ public static KeepalivePacketData nattKeepalivePacket(
+ InetAddress srcAddress, int srcPort,
+ InetAddress dstAddress, int dstPort) throws InvalidPacketException {
+
+ if (!(srcAddress instanceof Inet4Address)) {
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ if (dstPort != NATT_PORT) {
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
+ }
+
+ int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
+ ByteBuffer buf = ByteBuffer.allocate(length);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.putShort((short) 0x4500); // IP version and TOS
+ buf.putShort((short) length);
+ buf.putInt(0); // ID, flags, offset
+ buf.put((byte) 64); // TTL
+ buf.put((byte) OsConstants.IPPROTO_UDP);
+ int ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // IP checksum
+ buf.put(srcAddress.getAddress());
+ buf.put(dstAddress.getAddress());
+ buf.putShort((short) srcPort);
+ buf.putShort((short) dstPort);
+ buf.putShort((short) (length - 20)); // UDP length
+ int udpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // UDP checksum
+ buf.put((byte) 0xff); // NAT-T keepalive
+ buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
+ buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
+
+ return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
new file mode 100644
index 0000000..c78f347
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.KeepalivePacketData;
+import com.android.server.connectivity.NetworkAgentInfo;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.PacketKeepalive;
+import android.net.LinkAddress;
+import android.net.NetworkAgent;
+import android.net.NetworkUtils;
+import android.net.util.IpUtils;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.os.RemoteException;
+import android.system.OsConstants;
+import android.util.Log;
+import android.util.Pair;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import static android.net.ConnectivityManager.PacketKeepalive.*;
+import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
+
+/**
+ * Manages packet keepalive requests.
+ *
+ * Provides methods to stop and start keepalive requests, and keeps track of keepalives across all
+ * networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its
+ * methods must be called only from the ConnectivityService handler thread.
+ */
+public class KeepaliveTracker {
+
+ private static final String TAG = "KeepaliveTracker";
+ private static final boolean DBG = true;
+
+ // TODO: Change this to a system-only permission.
+ public static final String PERMISSION = android.Manifest.permission.CHANGE_NETWORK_STATE;
+
+ /** Keeps track of keepalive requests. */
+ private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
+ new HashMap<> ();
+ private final Handler mConnectivityServiceHandler;
+
+ public KeepaliveTracker(Handler handler) {
+ mConnectivityServiceHandler = handler;
+ }
+
+ /**
+ * Tracks information about a packet keepalive.
+ *
+ * All information about this keepalive is known at construction time except the slot number,
+ * which is only returned when the hardware has successfully started the keepalive.
+ */
+ class KeepaliveInfo implements IBinder.DeathRecipient {
+ // Bookkeping data.
+ private final Messenger mMessenger;
+ private final IBinder mBinder;
+ private final int mUid;
+ private final int mPid;
+ private final NetworkAgentInfo mNai;
+
+ /** Keepalive slot. A small integer that identifies this keepalive among the ones handled
+ * by this network. */
+ private int mSlot = PacketKeepalive.NO_KEEPALIVE;
+
+ // Packet data.
+ private final KeepalivePacketData mPacket;
+ private final int mInterval;
+
+ // Whether the keepalive is started or not.
+ public boolean isStarted;
+
+ public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai,
+ KeepalivePacketData packet, int interval) {
+ mMessenger = messenger;
+ mBinder = binder;
+ mPid = Binder.getCallingPid();
+ mUid = Binder.getCallingUid();
+
+ mNai = nai;
+ mPacket = packet;
+ mInterval = interval;
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ public NetworkAgentInfo getNai() {
+ return mNai;
+ }
+
+ public String toString() {
+ return new StringBuffer("KeepaliveInfo [")
+ .append(" network=").append(mNai.network)
+ .append(" isStarted=").append(isStarted)
+ .append(" ")
+ .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort))
+ .append("->")
+ .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort))
+ .append(" interval=" + mInterval)
+ .append(" data=" + HexDump.toHexString(mPacket.data))
+ .append(" uid=").append(mUid).append(" pid=").append(mPid)
+ .append(" ]")
+ .toString();
+ }
+
+ /** Sends a message back to the application via its PacketKeepalive.Callback. */
+ void notifyMessenger(int slot, int err) {
+ KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err);
+ }
+
+ /** Called when the application process is killed. */
+ public void binderDied() {
+ // Not called from ConnectivityService handler thread, so send it a message.
+ mConnectivityServiceHandler.obtainMessage(
+ NetworkAgent.CMD_STOP_PACKET_KEEPALIVE,
+ mSlot, PacketKeepalive.BINDER_DIED, mNai.network).sendToTarget();
+ }
+
+ void unlinkDeathRecipient() {
+ if (mBinder != null) {
+ mBinder.unlinkToDeath(this, 0);
+ }
+ }
+
+ private int checkNetworkConnected() {
+ if (!mNai.networkInfo.isConnectedOrConnecting()) {
+ return ERROR_INVALID_NETWORK;
+ }
+ return SUCCESS;
+ }
+
+ private int checkSourceAddress() {
+ // Check that we have the source address.
+ for (InetAddress address : mNai.linkProperties.getAddresses()) {
+ if (address.equals(mPacket.srcAddress)) {
+ return SUCCESS;
+ }
+ }
+ return ERROR_INVALID_IP_ADDRESS;
+ }
+
+ private int checkInterval() {
+ return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL;
+ }
+
+ private int isValid() {
+ synchronized (mNai) {
+ int error = checkInterval();
+ if (error == SUCCESS) error = checkNetworkConnected();
+ if (error == SUCCESS) error = checkSourceAddress();
+ return error;
+ }
+ }
+
+ void start(int slot) {
+ int error = isValid();
+ if (error == SUCCESS) {
+ mSlot = slot;
+ Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
+ mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket);
+ } else {
+ notifyMessenger(NO_KEEPALIVE, error);
+ return;
+ }
+ }
+
+ void stop(int reason) {
+ int uid = Binder.getCallingUid();
+ if (uid != mUid && uid != Process.SYSTEM_UID) {
+ if (DBG) {
+ Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
+ }
+ }
+ if (isStarted) {
+ Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name());
+ mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot);
+ }
+ notifyMessenger(mSlot, reason);
+ unlinkDeathRecipient();
+ }
+ }
+
+ void notifyMessenger(Messenger messenger, int slot, int err) {
+ Message message = Message.obtain();
+ message.what = EVENT_PACKET_KEEPALIVE;
+ message.arg1 = slot;
+ message.arg2 = err;
+ message.obj = null;
+ try {
+ messenger.send(message);
+ } catch (RemoteException e) {
+ // Process died?
+ }
+ }
+
+ private int findFirstFreeSlot(NetworkAgentInfo nai) {
+ HashMap networkKeepalives = mKeepalives.get(nai);
+ if (networkKeepalives == null) {
+ networkKeepalives = new HashMap<Integer, KeepaliveInfo>();
+ mKeepalives.put(nai, networkKeepalives);
+ }
+
+ // Find the lowest-numbered free slot.
+ int slot;
+ for (slot = 0; slot < networkKeepalives.size(); slot++) {
+ if (networkKeepalives.get(slot) == null) {
+ return slot;
+ }
+ }
+ // No free slot, pick one at the end.
+
+ // HACK for broadcom hardware that does not support slot 0!
+ if (slot == 0) slot = 1;
+ return slot;
+ }
+
+ public void handleStartKeepalive(Message message) {
+ KeepaliveInfo ki = (KeepaliveInfo) message.obj;
+ NetworkAgentInfo nai = ki.getNai();
+ int slot = findFirstFreeSlot(nai);
+ mKeepalives.get(nai).put(slot, ki);
+ ki.start(slot);
+ }
+
+ public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
+ HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+ if (networkKeepalives != null) {
+ for (KeepaliveInfo ki : networkKeepalives.values()) {
+ ki.stop(reason);
+ }
+ networkKeepalives.clear();
+ mKeepalives.remove(nai);
+ }
+ }
+
+ public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
+ HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+ if (networkKeepalives == null) {
+ Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + nai.name());
+ return;
+ }
+ KeepaliveInfo ki = networkKeepalives.get(slot);
+ if (ki == null) {
+ Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + nai.name());
+ return;
+ }
+ ki.stop(reason);
+ networkKeepalives.remove(slot);
+ if (networkKeepalives.isEmpty()) {
+ mKeepalives.remove(nai);
+ }
+ }
+
+ public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
+ HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+ if (networkKeepalives != null) {
+ ArrayList<Pair<Integer, Integer>> invalidKeepalives = new ArrayList<>();
+ for (int slot : networkKeepalives.keySet()) {
+ int error = networkKeepalives.get(slot).isValid();
+ if (error != SUCCESS) {
+ invalidKeepalives.add(Pair.create(slot, error));
+ }
+ }
+ for (Pair<Integer, Integer> slotAndError: invalidKeepalives) {
+ handleStopKeepalive(nai, slotAndError.first, slotAndError.second);
+ }
+ }
+ }
+
+ public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) {
+ int slot = message.arg1;
+ int reason = message.arg2;
+
+ KeepaliveInfo ki = null;
+ try {
+ ki = mKeepalives.get(nai).get(slot);
+ } catch(NullPointerException e) {}
+ if (ki == null) {
+ Log.e(TAG, "Event for unknown keepalive " + slot + " on " + nai.name());
+ return;
+ }
+
+ if (reason == SUCCESS && !ki.isStarted) {
+ // Keepalive successfully started.
+ if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name());
+ ki.isStarted = true;
+ ki.notifyMessenger(slot, reason);
+ } else {
+ // Keepalive successfully stopped, or error.
+ ki.isStarted = false;
+ if (reason == SUCCESS) {
+ if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name());
+ } else {
+ if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason);
+ }
+ handleStopKeepalive(nai, slot, reason);
+ }
+ }
+
+ public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
+ IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
+ InetAddress srcAddress, dstAddress;
+ try {
+ srcAddress = NetworkUtils.numericToInetAddress(srcAddrString);
+ dstAddress = NetworkUtils.numericToInetAddress(dstAddrString);
+ } catch (IllegalArgumentException e) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_IP_ADDRESS);
+ return;
+ }
+
+ KeepalivePacketData packet;
+ try {
+ packet = KeepalivePacketData.nattKeepalivePacket(
+ srcAddress, srcPort, dstAddress, NATT_PORT);
+ } catch (KeepalivePacketData.InvalidPacketException e) {
+ notifyMessenger(messenger, NO_KEEPALIVE, e.error);
+ return;
+ }
+ KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds);
+ Log.d(TAG, "Created keepalive: " + ki.toString());
+ mConnectivityServiceHandler.obtainMessage(
+ NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget();
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Packet keepalives:");
+ pw.increaseIndent();
+ for (NetworkAgentInfo nai : mKeepalives.keySet()) {
+ pw.println(nai.name());
+ pw.increaseIndent();
+ for (int slot : mKeepalives.get(nai).keySet()) {
+ KeepaliveInfo ki = mKeepalives.get(nai).get(slot);
+ pw.println(slot + ": " + ki.toString());
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 39333f6..0029279 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -195,6 +195,12 @@
request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities);
}
+ public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) {
+ return created &&
+ request.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
+ networkCapabilities);
+ }
+
public boolean isVPN() {
return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
}