am ee4e84b3: am 2f835a61: Merge "Make copies of NetworkInfo to give out." into honeycomb-LTE
* commit 'ee4e84b3346b4160b7bd45c134ca7c0d7322b6af':
Make copies of NetworkInfo to give out.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5b8076e..530122c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -16,13 +16,16 @@
package android.net;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.os.Binder;
+import android.os.Build.VERSION_CODES;
import android.os.RemoteException;
+import android.provider.Settings;
import java.net.InetAddress;
-import java.net.UnknownHostException;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -40,8 +43,9 @@
* state of the available networks</li>
* </ol>
*/
-public class ConnectivityManager
-{
+public class ConnectivityManager {
+ private static final String TAG = "ConnectivityManager";
+
/**
* A change in network connectivity has occurred. A connection has either
* been established or lost. The NetworkInfo for the affected network is
@@ -66,11 +70,28 @@
* is set to {@code true} if there are no connected networks at all.
*/
public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+
+ /**
+ * Identical to {@link #CONNECTIVITY_ACTION} broadcast, but sent without any
+ * applicable {@link Settings.Secure#CONNECTIVITY_CHANGE_DELAY}.
+ *
+ * @hide
+ */
+ public static final String CONNECTIVITY_ACTION_IMMEDIATE =
+ "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE";
+
/**
* The lookup key for a {@link NetworkInfo} object. Retrieve with
* {@link android.content.Intent#getParcelableExtra(String)}.
+ *
+ * @deprecated Since {@link NetworkInfo} can vary based on UID, applications
+ * should always obtain network information through
+ * {@link #getActiveNetworkInfo()} or
+ * {@link #getAllNetworkInfo()}.
*/
+ @Deprecated
public static final String EXTRA_NETWORK_INFO = "networkInfo";
+
/**
* The lookup key for a boolean that indicates whether a connect event
* is for a network to which the connectivity manager was failing over
@@ -109,7 +130,7 @@
* The lookup key for an int that provides information about
* our connection to the internet at large. 0 indicates no connection,
* 100 indicates a great connection. Retrieve it with
- * {@link android.content.Intent@getIntExtra(String)}.
+ * {@link android.content.Intent#getIntExtra(String, int)}.
* {@hide}
*/
public static final String EXTRA_INET_CONDITION = "inetCondition";
@@ -120,13 +141,12 @@
* <p>
* If an application uses the network in the background, it should listen
* for this broadcast and stop using the background data if the value is
- * false.
+ * {@code false}.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
-
/**
* Broadcast Action: The network connection may not be good
* uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
@@ -222,7 +242,9 @@
*/
public static final int TYPE_BLUETOOTH = 7;
- /** {@hide} */
+ /**
+ * Dummy data connection. This should not be used on shipping devices.
+ */
public static final int TYPE_DUMMY = 8;
/**
@@ -230,6 +252,7 @@
* will use this connection by default.
*/
public static final int TYPE_ETHERNET = 9;
+
/**
* Over the air Adminstration.
* {@hide}
@@ -248,20 +271,80 @@
*/
public static final int TYPE_MOBILE_CBS = 12;
- /** {@hide} */
- public static final int MAX_RADIO_TYPE = TYPE_MOBILE_CBS;
+ /**
+ * A Wi-Fi p2p connection. Only requesting processes will have access to
+ * the peers connected.
+ * {@hide}
+ */
+ public static final int TYPE_WIFI_P2P = 13;
/** {@hide} */
- public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_CBS;
+ public static final int MAX_RADIO_TYPE = TYPE_WIFI_P2P;
+
+ /** {@hide} */
+ public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P;
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
- private IConnectivityManager mService;
+ private final IConnectivityManager mService;
- static public boolean isNetworkTypeValid(int networkType) {
+ public static boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
}
+ /** {@hide} */
+ public static String getNetworkTypeName(int type) {
+ switch (type) {
+ case TYPE_MOBILE:
+ return "MOBILE";
+ case TYPE_WIFI:
+ return "WIFI";
+ case TYPE_MOBILE_MMS:
+ return "MOBILE_MMS";
+ case TYPE_MOBILE_SUPL:
+ return "MOBILE_SUPL";
+ case TYPE_MOBILE_DUN:
+ return "MOBILE_DUN";
+ case TYPE_MOBILE_HIPRI:
+ return "MOBILE_HIPRI";
+ case TYPE_WIMAX:
+ return "WIMAX";
+ case TYPE_BLUETOOTH:
+ return "BLUETOOTH";
+ case TYPE_DUMMY:
+ return "DUMMY";
+ case TYPE_ETHERNET:
+ return "ETHERNET";
+ case TYPE_MOBILE_FOTA:
+ return "MOBILE_FOTA";
+ case TYPE_MOBILE_IMS:
+ return "MOBILE_IMS";
+ case TYPE_MOBILE_CBS:
+ return "MOBILE_CBS";
+ case TYPE_WIFI_P2P:
+ return "WIFI_P2P";
+ default:
+ return Integer.toString(type);
+ }
+ }
+
+ /** {@hide} */
+ public static boolean isNetworkTypeMobile(int networkType) {
+ switch (networkType) {
+ case TYPE_MOBILE:
+ case TYPE_MOBILE_MMS:
+ case TYPE_MOBILE_SUPL:
+ case TYPE_MOBILE_DUN:
+ case TYPE_MOBILE_HIPRI:
+ case TYPE_MOBILE_FOTA:
+ case TYPE_MOBILE_IMS:
+ case TYPE_MOBILE_CBS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public void setNetworkPreference(int preference) {
try {
mService.setNetworkPreference(preference);
@@ -285,6 +368,15 @@
}
}
+ /** {@hide} */
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ try {
+ return mService.getActiveNetworkInfoForUid(uid);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -301,7 +393,7 @@
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -310,7 +402,7 @@
}
}
- /** @hide */
+ /** {@hide} */
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkProperties(networkType);
@@ -422,16 +514,19 @@
* <p>
* All applications that have background services that use the network
* should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
+ * <p>
+ * As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of
+ * background data depends on several combined factors, and this method will
+ * always return {@code true}. Instead, when background data is unavailable,
+ * {@link #getActiveNetworkInfo()} will now appear disconnected.
*
* @return Whether background data usage is allowed.
*/
+ @Deprecated
public boolean getBackgroundDataSetting() {
- try {
- return mService.getBackgroundDataSetting();
- } catch (RemoteException e) {
- // Err on the side of safety
- return false;
- }
+ // assume that background data is allowed; final authority is
+ // NetworkInfo which may be blocked.
+ return true;
}
/**
@@ -444,10 +539,21 @@
* @see #getBackgroundDataSetting()
* @hide
*/
+ @Deprecated
public void setBackgroundDataSetting(boolean allowBackgroundData) {
+ // ignored
+ }
+
+ /**
+ * Return quota status for the current active network, or {@code null} if no
+ * network is active. Quota status can change rapidly, so these values
+ * shouldn't be cached.
+ */
+ public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
try {
- mService.setBackgroundDataSetting(allowBackgroundData);
+ return mService.getActiveNetworkQuotaInfo();
} catch (RemoteException e) {
+ return null;
}
}
@@ -480,21 +586,10 @@
}
/**
- * Don't allow use of default constructor.
- */
- @SuppressWarnings({"UnusedDeclaration"})
- private ConnectivityManager() {
- }
-
- /**
* {@hide}
*/
public ConnectivityManager(IConnectivityManager service) {
- if (service == null) {
- throw new IllegalArgumentException(
- "ConnectivityManager() cannot be constructed with null service");
- }
- mService = service;
+ mService = checkNotNull(service, "missing IConnectivityManager");
}
/**
@@ -598,6 +693,17 @@
}
}
+ /**
+ * {@hide}
+ */
+ public int setUsbTethering(boolean enable) {
+ try {
+ return mService.setUsbTethering(enable);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
+
/** {@hide} */
public static final int TETHER_ERROR_NO_ERROR = 0;
/** {@hide} */
@@ -708,4 +814,22 @@
} catch (RemoteException e) {
}
}
+
+ /**
+ * Returns true if the hardware supports the given network type
+ * else it returns false. This doesn't indicate we have coverage
+ * or are authorized onto a network, just whether or not the
+ * hardware supports it. For example a gsm phone without a sim
+ * should still return true for mobile data, but a wifi only tablet
+ * would return false.
+ * @param networkType The nework type we'd like to check
+ * @return true if supported, else false
+ * @hide
+ */
+ public boolean isNetworkSupported(int networkType) {
+ try {
+ return mService.isNetworkSupported(networkType);
+ } catch (RemoteException e) {}
+ return false;
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 8be492c..eef658e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -18,8 +18,14 @@
import android.net.LinkProperties;
import android.net.NetworkInfo;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkState;
import android.net.ProxyProperties;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
/**
* Interface that answers queries about, and allows changing, the
@@ -33,15 +39,19 @@
int getNetworkPreference();
NetworkInfo getActiveNetworkInfo();
-
+ NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
-
NetworkInfo[] getAllNetworkInfo();
- LinkProperties getActiveLinkProperties();
+ boolean isNetworkSupported(int networkType);
+ LinkProperties getActiveLinkProperties();
LinkProperties getLinkProperties(int networkType);
+ NetworkState[] getAllNetworkState();
+
+ NetworkQuotaInfo getActiveNetworkQuotaInfo();
+
boolean setRadios(boolean onOff);
boolean setRadio(int networkType, boolean turnOn);
@@ -55,14 +65,12 @@
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
- boolean getBackgroundDataSetting();
-
- void setBackgroundDataSetting(boolean allowBackgroundData);
-
boolean getMobileDataEnabled();
-
void setMobileDataEnabled(boolean enabled);
+ /** Policy control over specific {@link NetworkStateTracker}. */
+ void setPolicyDataEnable(int networkType, boolean enabled);
+
int tether(String iface);
int untether(String iface);
@@ -83,6 +91,8 @@
String[] getTetherableBluetoothRegexs();
+ int setUsbTethering(boolean enable);
+
void requestNetworkTransitionWakelock(in String forWhom);
void reportInetCondition(int networkType, int percentage);
@@ -94,4 +104,14 @@
ProxyProperties getProxy();
void setDataDependency(int networkType, boolean met);
+
+ boolean protectVpn(in ParcelFileDescriptor socket);
+
+ boolean prepareVpn(String oldPackage, String newPackage);
+
+ ParcelFileDescriptor establishVpn(in VpnConfig config);
+
+ void startLegacyVpn(in VpnConfig config, in String[] racoon, in String[] mtpd);
+
+ LegacyVpnInfo getLegacyVpnInfo();
}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 65a11cc..537750a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -74,7 +74,9 @@
/** IP traffic not available. */
DISCONNECTED,
/** Attempt to connect failed. */
- FAILED
+ FAILED,
+ /** Access to this network is blocked. */
+ BLOCKED
}
/**
@@ -96,6 +98,7 @@
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+ stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
}
private int mNetworkType;
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
new file mode 100644
index 0000000..704111b
--- /dev/null
+++ b/core/java/android/net/NetworkState.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Snapshot of network state.
+ *
+ * @hide
+ */
+public class NetworkState implements Parcelable {
+
+ public final NetworkInfo networkInfo;
+ public final LinkProperties linkProperties;
+ public final LinkCapabilities linkCapabilities;
+ /** Currently only used by testing. */
+ public final String subscriberId;
+
+ public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+ LinkCapabilities linkCapabilities) {
+ this(networkInfo, linkProperties, linkCapabilities, null);
+ }
+
+ public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+ LinkCapabilities linkCapabilities, String subscriberId) {
+ this.networkInfo = networkInfo;
+ this.linkProperties = linkProperties;
+ this.linkCapabilities = linkCapabilities;
+ this.subscriberId = subscriberId;
+ }
+
+ public NetworkState(Parcel in) {
+ networkInfo = in.readParcelable(null);
+ linkProperties = in.readParcelable(null);
+ linkCapabilities = in.readParcelable(null);
+ subscriberId = in.readString();
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(networkInfo, flags);
+ out.writeParcelable(linkProperties, flags);
+ out.writeParcelable(linkCapabilities, flags);
+ out.writeString(subscriberId);
+ }
+
+ public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
+ public NetworkState createFromParcel(Parcel in) {
+ return new NetworkState(in);
+ }
+
+ public NetworkState[] newArray(int size) {
+ return new NetworkState[size];
+ }
+ };
+
+}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 068fe67..d9bd50e 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -1,16 +1,16 @@
/*
* Copyright 2008, 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
+ * 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
+ * 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
+ * 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.
*/
@@ -61,7 +61,6 @@
* to look them up every time.
*/
static struct fieldIds {
- jclass dhcpInfoInternalClass;
jmethodID constructorId;
jfieldID ipaddress;
jfieldID prefixLength;
@@ -130,7 +129,7 @@
}
env->ReleaseStringUTFChars(ifname, nameStr);
- if (result == 0 && dhcpInfoInternalFieldIds.dhcpInfoInternalClass != NULL) {
+ if (result == 0) {
env->SetObjectField(info, dhcpInfoInternalFieldIds.ipaddress, env->NewStringUTF(ipaddr));
// set the gateway
@@ -222,19 +221,15 @@
int register_android_net_NetworkUtils(JNIEnv* env)
{
- jclass netutils = env->FindClass(NETUTILS_PKG_NAME);
- LOG_FATAL_IF(netutils == NULL, "Unable to find class " NETUTILS_PKG_NAME);
-
- dhcpInfoInternalFieldIds.dhcpInfoInternalClass = env->FindClass("android/net/DhcpInfoInternal");
- if (dhcpInfoInternalFieldIds.dhcpInfoInternalClass != NULL) {
- dhcpInfoInternalFieldIds.constructorId = env->GetMethodID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "<init>", "()V");
- dhcpInfoInternalFieldIds.ipaddress = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "ipAddress", "Ljava/lang/String;");
- dhcpInfoInternalFieldIds.prefixLength = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "prefixLength", "I");
- dhcpInfoInternalFieldIds.dns1 = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "dns1", "Ljava/lang/String;");
- dhcpInfoInternalFieldIds.dns2 = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "dns2", "Ljava/lang/String;");
- dhcpInfoInternalFieldIds.serverAddress = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "serverAddress", "Ljava/lang/String;");
- dhcpInfoInternalFieldIds.leaseDuration = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "leaseDuration", "I");
- }
+ jclass dhcpInfoInternalClass = env->FindClass("android/net/DhcpInfoInternal");
+ LOG_FATAL_IF(dhcpInfoInternalClass == NULL, "Unable to find class android/net/DhcpInfoInternal");
+ dhcpInfoInternalFieldIds.constructorId = env->GetMethodID(dhcpInfoInternalClass, "<init>", "()V");
+ dhcpInfoInternalFieldIds.ipaddress = env->GetFieldID(dhcpInfoInternalClass, "ipAddress", "Ljava/lang/String;");
+ dhcpInfoInternalFieldIds.prefixLength = env->GetFieldID(dhcpInfoInternalClass, "prefixLength", "I");
+ dhcpInfoInternalFieldIds.dns1 = env->GetFieldID(dhcpInfoInternalClass, "dns1", "Ljava/lang/String;");
+ dhcpInfoInternalFieldIds.dns2 = env->GetFieldID(dhcpInfoInternalClass, "dns2", "Ljava/lang/String;");
+ dhcpInfoInternalFieldIds.serverAddress = env->GetFieldID(dhcpInfoInternalClass, "serverAddress", "Ljava/lang/String;");
+ dhcpInfoInternalFieldIds.leaseDuration = env->GetFieldID(dhcpInfoInternalClass, "leaseDuration", "I");
return AndroidRuntime::registerNativeMethods(env,
NETUTILS_PKG_NAME, gNetworkUtilMethods, NELEM(gNetworkUtilMethods));
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
new file mode 100644
index 0000000..e1db073
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2011 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 android.net;
+
+import static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkStatsHistory.FIELD_OPERATIONS;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_RX_PACKETS;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import com.android.frameworks.coretests.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.Random;
+
+@SmallTest
+public class NetworkStatsHistoryTest extends AndroidTestCase {
+ private static final String TAG = "NetworkStatsHistoryTest";
+
+ private static final long TEST_START = 1194220800000L;
+
+ private static final long KB_IN_BYTES = 1024;
+ private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+ private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+
+ private NetworkStatsHistory stats;
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if (stats != null) {
+ assertConsistent(stats);
+ }
+ }
+
+ public void testReadOriginalVersion() throws Exception {
+ final DataInputStream in = new DataInputStream(
+ getContext().getResources().openRawResource(R.raw.history_v1));
+
+ NetworkStatsHistory.Entry entry = null;
+ try {
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+ assertEquals(15 * SECOND_IN_MILLIS, history.getBucketDuration());
+
+ entry = history.getValues(0, entry);
+ assertEquals(29143L, entry.rxBytes);
+ assertEquals(6223L, entry.txBytes);
+
+ entry = history.getValues(history.size() - 1, entry);
+ assertEquals(1476L, entry.rxBytes);
+ assertEquals(838L, entry.txBytes);
+
+ entry = history.getValues(Long.MIN_VALUE, Long.MAX_VALUE, entry);
+ assertEquals(332401L, entry.rxBytes);
+ assertEquals(64314L, entry.txBytes);
+
+ } finally {
+ in.close();
+ }
+ }
+
+ public void testRecordSingleBucket() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record data into narrow window to get single bucket
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ assertEquals(1, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 1024L, 10L, 2048L, 20L, 2L);
+ }
+
+ public void testRecordEqualBuckets() throws Exception {
+ final long bucketDuration = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(bucketDuration);
+
+ // split equally across two buckets
+ final long recordStart = TEST_START + (bucketDuration / 2);
+ stats.recordData(recordStart, recordStart + bucketDuration,
+ new NetworkStats.Entry(1024L, 10L, 128L, 2L, 2L));
+
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L);
+ assertValues(stats, 1, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L);
+ }
+
+ public void testRecordTouchingBuckets() throws Exception {
+ final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // split almost completely into middle bucket, but with a few minutes
+ // overlap into neighboring buckets. total record is 20 minutes.
+ final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
+ final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
+ stats.recordData(recordStart, recordEnd,
+ new NetworkStats.Entry(1000L, 2000L, 5000L, 10000L, 100L));
+
+ assertEquals(3, stats.size());
+ // first bucket should have (1/20 of value)
+ assertValues(stats, 0, MINUTE_IN_MILLIS, 50L, 100L, 250L, 500L, 5L);
+ // second bucket should have (15/20 of value)
+ assertValues(stats, 1, 15 * MINUTE_IN_MILLIS, 750L, 1500L, 3750L, 7500L, 75L);
+ // final bucket should have (4/20 of value)
+ assertValues(stats, 2, 4 * MINUTE_IN_MILLIS, 200L, 400L, 1000L, 2000L, 20L);
+ }
+
+ public void testRecordGapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record some data today and next week with large gap
+ final long firstStart = TEST_START;
+ final long lastStart = TEST_START + WEEK_IN_MILLIS;
+ stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(128L, 2L, 256L, 4L, 1L));
+ stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(64L, 1L, 512L, 8L, 2L));
+
+ // we should have two buckets, far apart from each other
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L);
+ assertValues(stats, 1, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L);
+
+ // now record something in middle, spread across two buckets
+ final long middleStart = TEST_START + DAY_IN_MILLIS;
+ final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
+ stats.recordData(middleStart, middleEnd,
+ new NetworkStats.Entry(2048L, 4L, 2048L, 4L, 2L));
+
+ // now should have four buckets, with new record in middle two buckets
+ assertEquals(4, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L);
+ assertValues(stats, 1, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L);
+ assertValues(stats, 2, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L);
+ assertValues(stats, 3, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L);
+ }
+
+ public void testRecordOverlapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record some data in one bucket, and another overlapping buckets
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+ new NetworkStats.Entry(256L, 2L, 256L, 2L, 1L));
+ final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
+ stats.recordData(midStart, midStart + HOUR_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 1024L, 10L, 10L));
+
+ // should have two buckets, with some data mixed together
+ assertEquals(2, stats.size());
+ assertValues(stats, 0, SECOND_IN_MILLIS + (HOUR_IN_MILLIS / 2), 768L, 7L, 768L, 7L, 6L);
+ assertValues(stats, 1, (HOUR_IN_MILLIS / 2), 512L, 5L, 512L, 5L, 5L);
+ }
+
+ public void testRecordEntireGapIdentical() throws Exception {
+ // first, create two separate histories far apart
+ final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats1.recordData(TEST_START, TEST_START + 2 * HOUR_IN_MILLIS, 2000L, 1000L);
+
+ final long TEST_START_2 = TEST_START + DAY_IN_MILLIS;
+ final NetworkStatsHistory stats2 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats2.recordData(TEST_START_2, TEST_START_2 + 2 * HOUR_IN_MILLIS, 1000L, 500L);
+
+ // combine together with identical bucket size
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 3000L, 1500L);
+
+ // now inspect internal buckets
+ assertValues(stats, 0, 1000L, 500L);
+ assertValues(stats, 1, 1000L, 500L);
+ assertValues(stats, 2, 500L, 250L);
+ assertValues(stats, 3, 500L, 250L);
+ }
+
+ public void testRecordEntireOverlapVaryingBuckets() throws Exception {
+ // create history just over hour bucket boundary
+ final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ stats1.recordData(TEST_START, TEST_START + MINUTE_IN_MILLIS * 60, 600L, 600L);
+
+ final long TEST_START_2 = TEST_START + MINUTE_IN_MILLIS;
+ final NetworkStatsHistory stats2 = new NetworkStatsHistory(MINUTE_IN_MILLIS);
+ stats2.recordData(TEST_START_2, TEST_START_2 + MINUTE_IN_MILLIS * 5, 50L, 50L);
+
+ // combine together with minute bucket size
+ stats = new NetworkStatsHistory(MINUTE_IN_MILLIS);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
+
+ // now inspect internal buckets
+ assertValues(stats, 0, 10L, 10L);
+ assertValues(stats, 1, 20L, 20L);
+ assertValues(stats, 2, 20L, 20L);
+ assertValues(stats, 3, 20L, 20L);
+ assertValues(stats, 4, 20L, 20L);
+ assertValues(stats, 5, 20L, 20L);
+ assertValues(stats, 6, 10L, 10L);
+
+ // now combine using 15min buckets
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS / 4);
+ stats.recordEntireHistory(stats1);
+ stats.recordEntireHistory(stats2);
+
+ // first verify that totals match up
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
+
+ // and inspect buckets
+ assertValues(stats, 0, 200L, 200L);
+ assertValues(stats, 1, 150L, 150L);
+ assertValues(stats, 2, 150L, 150L);
+ assertValues(stats, 3, 150L, 150L);
+ }
+
+ public void testRemove() throws Exception {
+ stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
+
+ // record some data across 24 buckets
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
+ assertEquals(24, stats.size());
+
+ // try removing far before buckets; should be no change
+ stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing just moments into first bucket; should be no change
+ // since that bucket contains data beyond the cutoff
+ stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+ assertEquals(24, stats.size());
+
+ // try removing single bucket
+ stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+ assertEquals(23, stats.size());
+
+ // try removing multiple buckets
+ stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+ assertEquals(20, stats.size());
+
+ // try removing all buckets
+ stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+ assertEquals(0, stats.size());
+ }
+
+ public void testTotalData() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ // record uniform data across day
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 2400L, 4800L);
+
+ // verify that total outside range is 0
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START - DAY_IN_MILLIS, 0L, 0L);
+
+ // verify total in first hour
+ assertValues(stats, TEST_START, TEST_START + HOUR_IN_MILLIS, 100L, 200L);
+
+ // verify total across 1.5 hours
+ assertValues(stats, TEST_START, TEST_START + (long) (1.5 * HOUR_IN_MILLIS), 150L, 300L);
+
+ // verify total beyond end
+ assertValues(stats, TEST_START + (23 * HOUR_IN_MILLIS), TEST_START + WEEK_IN_MILLIS, 100L, 200L);
+
+ // verify everything total
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 2400L, 4800L);
+
+ }
+
+ @Suppress
+ public void testFuzzing() throws Exception {
+ try {
+ // fuzzing with random events, looking for crashes
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final Random r = new Random();
+ for (int i = 0; i < 500; i++) {
+ stats = new NetworkStatsHistory(r.nextLong());
+ for (int j = 0; j < 10000; j++) {
+ if (r.nextBoolean()) {
+ // add range
+ final long start = r.nextLong();
+ final long end = start + r.nextInt();
+ entry.rxBytes = nextPositiveLong(r);
+ entry.rxPackets = nextPositiveLong(r);
+ entry.txBytes = nextPositiveLong(r);
+ entry.txPackets = nextPositiveLong(r);
+ entry.operations = nextPositiveLong(r);
+ stats.recordData(start, end, entry);
+ } else {
+ // trim something
+ stats.removeBucketsBefore(r.nextLong());
+ }
+ }
+ assertConsistent(stats);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, String.valueOf(stats));
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static long nextPositiveLong(Random r) {
+ final long value = r.nextLong();
+ return value < 0 ? -value : value;
+ }
+
+ public void testIgnoreFields() throws Exception {
+ final NetworkStatsHistory history = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+ history.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ history.recordData(0, 2 * MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(2L, 2L, 2L, 2L, 2L));
+
+ assertFullValues(history, UNKNOWN, 1026L, UNKNOWN, 2050L, UNKNOWN, UNKNOWN);
+ }
+
+ public void testIgnoreFieldsRecordIn() throws Exception {
+ final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+ final NetworkStatsHistory partial = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+ full.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ partial.recordEntireHistory(full);
+
+ assertFullValues(partial, UNKNOWN, UNKNOWN, 10L, UNKNOWN, UNKNOWN, 4L);
+ }
+
+ public void testIgnoreFieldsRecordOut() throws Exception {
+ final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+ final NetworkStatsHistory partial = new NetworkStatsHistory(
+ MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+ partial.recordData(0, MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ full.recordEntireHistory(partial);
+
+ assertFullValues(full, MINUTE_IN_MILLIS, 0L, 10L, 0L, 0L, 4L);
+ }
+
+ public void testSerialize() throws Exception {
+ final NetworkStatsHistory before = new NetworkStatsHistory(MINUTE_IN_MILLIS, 40, FIELD_ALL);
+ before.recordData(0, 4 * MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+ before.recordData(DAY_IN_MILLIS, DAY_IN_MILLIS + MINUTE_IN_MILLIS,
+ new NetworkStats.Entry(10L, 20L, 30L, 40L, 50L));
+
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ before.writeToStream(new DataOutputStream(out));
+ out.close();
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ final NetworkStatsHistory after = new NetworkStatsHistory(new DataInputStream(in));
+
+ // must have identical totals before and after
+ assertFullValues(before, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L);
+ assertFullValues(after, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L);
+ }
+
+ public void testVarLong() throws Exception {
+ assertEquals(0L, performVarLong(0L));
+ assertEquals(-1L, performVarLong(-1L));
+ assertEquals(1024L, performVarLong(1024L));
+ assertEquals(-1024L, performVarLong(-1024L));
+ assertEquals(40 * MB_IN_BYTES, performVarLong(40 * MB_IN_BYTES));
+ assertEquals(512 * GB_IN_BYTES, performVarLong(512 * GB_IN_BYTES));
+ assertEquals(Long.MIN_VALUE, performVarLong(Long.MIN_VALUE));
+ assertEquals(Long.MAX_VALUE, performVarLong(Long.MAX_VALUE));
+ assertEquals(Long.MIN_VALUE + 40, performVarLong(Long.MIN_VALUE + 40));
+ assertEquals(Long.MAX_VALUE - 40, performVarLong(Long.MAX_VALUE - 40));
+ }
+
+ public void testIndexBeforeAfter() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = new NetworkStatsHistory(BUCKET_SIZE);
+
+ final long FIRST_START = TEST_START;
+ final long FIRST_END = FIRST_START + (2 * HOUR_IN_MILLIS);
+ final long SECOND_START = TEST_START + WEEK_IN_MILLIS;
+ final long SECOND_END = SECOND_START + HOUR_IN_MILLIS;
+ final long THIRD_START = TEST_START + (2 * WEEK_IN_MILLIS);
+ final long THIRD_END = THIRD_START + (2 * HOUR_IN_MILLIS);
+
+ stats.recordData(FIRST_START, FIRST_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(SECOND_START, SECOND_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+ stats.recordData(THIRD_START, THIRD_END,
+ new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
+
+ // should have buckets: 2+1+2
+ assertEquals(5, stats.size());
+
+ assertIndexBeforeAfter(stats, 0, 0, Long.MIN_VALUE);
+ assertIndexBeforeAfter(stats, 0, 1, FIRST_START);
+ assertIndexBeforeAfter(stats, 0, 1, FIRST_START + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 0, 2, FIRST_START + HOUR_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_START + HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END);
+ assertIndexBeforeAfter(stats, 1, 2, FIRST_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 2, SECOND_START - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 1, 3, SECOND_START);
+ assertIndexBeforeAfter(stats, 2, 3, SECOND_END);
+ assertIndexBeforeAfter(stats, 2, 3, SECOND_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 2, 3, THIRD_START - MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 2, 4, THIRD_START);
+ assertIndexBeforeAfter(stats, 3, 4, THIRD_START + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 3, 4, THIRD_START + HOUR_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 4, 4, THIRD_END);
+ assertIndexBeforeAfter(stats, 4, 4, THIRD_END + MINUTE_IN_MILLIS);
+ assertIndexBeforeAfter(stats, 4, 4, Long.MAX_VALUE);
+ }
+
+ private static void assertIndexBeforeAfter(
+ NetworkStatsHistory stats, int before, int after, long time) {
+ assertEquals("unexpected before", before, stats.getIndexBefore(time));
+ assertEquals("unexpected after", after, stats.getIndexAfter(time));
+ }
+
+ private static long performVarLong(long before) throws Exception {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeVarLong(new DataOutputStream(out), before);
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ return readVarLong(new DataInputStream(in));
+ }
+
+ private static void assertConsistent(NetworkStatsHistory stats) {
+ // verify timestamps are monotonic
+ long lastStart = Long.MIN_VALUE;
+ NetworkStatsHistory.Entry entry = null;
+ for (int i = 0; i < stats.size(); i++) {
+ entry = stats.getValues(i, entry);
+ assertTrue(lastStart < entry.bucketStart);
+ lastStart = entry.bucketStart;
+ }
+ }
+
+ private static void assertValues(
+ NetworkStatsHistory stats, int index, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertValues(
+ NetworkStatsHistory stats, long start, long end, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, int index, long activeTime,
+ long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
+ assertEquals("unexpected activeTime", activeTime, entry.activeTime);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static void assertFullValues(NetworkStatsHistory stats, long activeTime, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ assertValues(stats, Long.MIN_VALUE, Long.MAX_VALUE, activeTime, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, long start, long end,
+ long activeTime, long rxBytes, long rxPackets, long txBytes, long txPackets,
+ long operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected activeTime", activeTime, entry.activeTime);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
new file mode 100644
index 0000000..c36685d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 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 android.net;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class NetworkStatsTest extends TestCase {
+
+ private static final String TEST_IFACE = "test0";
+ private static final String TEST_IFACE2 = "test2";
+ private static final int TEST_UID = 1001;
+ private static final long TEST_START = 1194220800000L;
+
+ public void testFindIndex() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 11)
+ .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12);
+
+ assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE));
+ assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE));
+ assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE));
+ assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE));
+ }
+
+ public void testAddEntryGrow() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 2);
+
+ assertEquals(0, stats.size());
+ assertEquals(2, stats.internalSize());
+
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 2L, 3);
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 2L, 2L, 2L, 2L, 4);
+
+ assertEquals(2, stats.size());
+ assertEquals(2, stats.internalSize());
+
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 3L, 30L, 4L, 40L, 7);
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 4L, 40L, 4L, 40L, 8);
+ stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 5L, 50L, 5L, 50L, 10);
+
+ assertEquals(5, stats.size());
+ assertTrue(stats.internalSize() >= 5);
+
+ assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 2L, 3);
+ assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 2L, 2L, 2L, 2L, 4);
+ assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 3L, 30L, 4L, 40L, 7);
+ assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 4L, 40L, 4L, 40L, 8);
+ assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 5L, 50L, 5L, 50L, 10);
+ }
+
+ public void testCombineExisting() throws Exception {
+ final NetworkStats stats = new NetworkStats(TEST_START, 10);
+
+ stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
+ stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+ stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L, -128L, -1L, -1);
+
+ assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 384L, 3L, 128L, 1L, 9);
+ assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+
+ // now try combining that should create row
+ stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+ assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 256L, 2L, 256L, 2L, 6);
+ }
+
+ public void testSubtractIdenticalData() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats result = after.subtract(before);
+
+ // identical data should result in zero delta
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ }
+
+ public void testSubtractIdenticalRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
+
+ final NetworkStats result = after.subtract(before);
+
+ // expect delta between measurements
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 1L, 4);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 4L, 1L, 8);
+ }
+
+ public void testSubtractNewRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
+ .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+
+ final NetworkStats result = after.subtract(before);
+
+ // its okay to have new rows
+ assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+ assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+ }
+
+ public void testSubtractMissingRows() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
+ .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
+
+ final NetworkStats after = new NetworkStats(TEST_START, 1)
+ .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
+
+ final NetworkStats result = after.subtract(before);
+
+ // should silently drop omitted rows
+ assertEquals(1, result.size());
+ assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 1L, 2L, 3L, 4L, 0);
+ assertEquals(4L, result.getTotalBytes());
+ }
+
+ public void testTotalBytes() throws Exception {
+ final NetworkStats iface = new NetworkStats(TEST_START, 2)
+ .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
+ assertEquals(384L, iface.getTotalBytes());
+
+ final NetworkStats uidSet = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+ assertEquals(96L, uidSet.getTotalBytes());
+
+ final NetworkStats uidTag = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
+ assertEquals(64L, uidTag.getTotalBytes());
+ }
+
+ private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) {
+ final NetworkStats.Entry entry = stats.getValues(index, null);
+ assertEquals(iface, entry.iface);
+ assertEquals(uid, entry.uid);
+ assertEquals(set, entry.set);
+ assertEquals(tag, entry.tag);
+ assertEquals(rxBytes, entry.rxBytes);
+ assertEquals(rxPackets, entry.rxPackets);
+ assertEquals(txBytes, entry.txBytes);
+ assertEquals(txPackets, entry.txPackets);
+ assertEquals(operations, entry.operations);
+ }
+
+}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 557cf1f..2348d76 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,6 +16,13 @@
package com.android.server;
+import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,26 +33,32 @@
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
import android.net.IConnectivityManager;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.MobileDataStateTracker;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkState;
import android.net.NetworkStateTracker;
import android.net.NetworkUtils;
import android.net.Proxy;
import android.net.ProxyProperties;
import android.net.RouteInfo;
-import android.net.vpn.VpnManager;
import android.net.wifi.WifiStateTracker;
import android.os.Binder;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -54,22 +67,28 @@
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
+import android.util.SparseIntArray;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
import com.android.internal.telephony.Phone;
import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.Vpn;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
import java.io.FileDescriptor;
-import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
-import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.GregorianCalendar;
+import java.util.HashSet;
import java.util.List;
/**
@@ -81,6 +100,8 @@
private static final boolean VDBG = true;
private static final String TAG = "ConnectivityService";
+ private static final boolean LOGD_RULES = false;
+
// how long to wait before switching back to a radio's default network
private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
// system property that can override the above value
@@ -94,6 +115,15 @@
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
+ private Vpn mVpn;
+
+ /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
+ private Object mRulesLock = new Object();
+ /** Currently active network rules by UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
+ /** Set of ifaces that are costly. */
+ private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+
/**
* Sometimes we want to refer to the individual network state
* trackers separately, and sometimes we just want to treat them
@@ -112,8 +142,6 @@
*/
private List mNetRequestersPids[];
- private WifiWatchdogService mWifiWatchdogService;
-
// priority order of the nettrackers
// (excluding dynamically set mNetworkPreference)
// TODO - move mNetworkTypePreference into this
@@ -128,14 +156,15 @@
private boolean mInetConditionChangeInFlight = false;
private int mDefaultConnectionSequence = 0;
+ private Object mDnsLock = new Object();
private int mNumDnsEntries;
+ private boolean mDnsOverridden = false;
private boolean mTestMode;
private static ConnectivityService sServiceInstance;
- private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true);
-
private INetworkManagementService mNetd;
+ private INetworkPolicyManager mPolicyManager;
private static final int ENABLED = 1;
private static final int DISABLED = 0;
@@ -182,13 +211,6 @@
MAX_NETWORK_STATE_TRACKER_EVENT + 5;
/**
- * used internally to set the background data preference
- * arg1 = TRUE for enabled, FALSE for disabled
- */
- private static final int EVENT_SET_BACKGROUND_DATA =
- MAX_NETWORK_STATE_TRACKER_EVENT + 6;
-
- /**
* used internally to set enable/disable cellular data
* arg1 = ENBALED or DISABLED
*/
@@ -217,10 +239,23 @@
MAX_NETWORK_STATE_TRACKER_EVENT + 10;
/**
+ * used internally to restore DNS properties back to the
+ * default network
+ */
+ private static final int EVENT_RESTORE_DNS =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 11;
+
+ /**
* used internally to send a sticky broadcast delayed.
*/
private static final int EVENT_SEND_STICKY_BROADCAST_INTENT =
- MAX_NETWORK_STATE_TRACKER_EVENT + 11;
+ MAX_NETWORK_STATE_TRACKER_EVENT + 12;
+
+ /**
+ * Used internally to
+ * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
+ */
+ private static final int EVENT_SET_POLICY_DATA_ENABLE = MAX_NETWORK_STATE_TRACKER_EVENT + 13;
private Handler mHandler;
@@ -271,23 +306,14 @@
// the set of network types that can only be enabled by system/sig apps
List mProtectedNetworks;
- public static synchronized ConnectivityService getInstance(Context context) {
- if (sServiceInstance == null) {
- sServiceInstance = new ConnectivityService(context);
- }
- return sServiceInstance;
- }
-
- private ConnectivityService(Context context) {
+ public ConnectivityService(
+ Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
if (DBG) log("ConnectivityService starting up");
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
handlerThread.start();
mHandler = new MyHandler(handlerThread.getLooper());
- mBackgroundDataEnabled.set(Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.BACKGROUND_DATA, 1) == 1);
-
// setup our unique device name
if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
String id = Settings.Secure.getString(context.getContentResolver(),
@@ -311,9 +337,19 @@
loge("Error setting defaultDns using " + dns);
}
- mContext = context;
+ mContext = checkNotNull(context, "missing Context");
+ mNetd = checkNotNull(netd, "missing INetworkManagementService");
+ mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
- PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ try {
+ mPolicyManager.registerListener(mPolicyListener);
+ } catch (RemoteException e) {
+ // ouch, no rules updates means some processes may never get network
+ loge("unable to register INetworkPolicyListener" + e.toString());
+ }
+
+ final PowerManager powerManager = (PowerManager) context.getSystemService(
+ Context.POWER_SERVICE);
mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_networkTransitionTimeout);
@@ -426,18 +462,10 @@
for (int netType : mPriorityList) {
switch (mNetConfigs[netType].radio) {
case ConnectivityManager.TYPE_WIFI:
- if (DBG) log("Starting Wifi Service.");
- WifiStateTracker wst = new WifiStateTracker();
- WifiService wifiService = new WifiService(context);
- ServiceManager.addService(Context.WIFI_SERVICE, wifiService);
- wifiService.checkAndStartWifi();
- mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
- wst.startMonitoring(context, mHandler);
-
- //TODO: as part of WWS refactor, create only when needed
- mWifiWatchdogService = new WifiWatchdogService(context);
-
- break;
+ mNetTrackers[netType] = new WifiStateTracker(netType,
+ mNetConfigs[netType].name);
+ mNetTrackers[netType].startMonitoring(context, mHandler);
+ break;
case ConnectivityManager.TYPE_MOBILE:
mNetTrackers[netType] = new MobileDataStateTracker(netType,
mNetConfigs[netType].name);
@@ -462,14 +490,27 @@
continue;
}
mCurrentLinkProperties[netType] = null;
+ if (mNetConfigs[netType].isDefault()) mNetTrackers[netType].reconnect();
}
- mTethering = new Tethering(mContext, mHandler.getLooper());
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b);
+
+ mTethering = new Tethering(mContext, nmService, mHandler.getLooper());
mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
mTethering.getTetherableWifiRegexs().length != 0 ||
mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceTypes().length != 0);
+ mVpn = new Vpn(mContext, new VpnCallback());
+
+ try {
+ nmService.registerObserver(mTethering);
+ nmService.registerObserver(mVpn);
+ } catch (RemoteException e) {
+ loge("Error registering observer :" + e);
+ }
+
if (DBG) {
mInetLog = new ArrayList();
}
@@ -478,11 +519,8 @@
mSettingsObserver.observe(mContext);
loadGlobalProxy();
-
- VpnManager.startVpnService(context);
}
-
/**
* Sets the preferred network.
* @param preference the new preference
@@ -576,34 +614,99 @@
}
/**
+ * Check if UID should be blocked from using the network represented by the
+ * given {@link NetworkStateTracker}.
+ */
+ private boolean isNetworkBlocked(NetworkStateTracker tracker, int uid) {
+ final String iface = tracker.getLinkProperties().getInterfaceName();
+
+ final boolean networkCostly;
+ final int uidRules;
+ synchronized (mRulesLock) {
+ networkCostly = mMeteredIfaces.contains(iface);
+ uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ }
+
+ if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
+ return true;
+ }
+
+ // no restrictive rules; network is visible
+ return false;
+ }
+
+ /**
+ * Return a filtered {@link NetworkInfo}, potentially marked
+ * {@link DetailedState#BLOCKED} based on
+ * {@link #isNetworkBlocked(NetworkStateTracker, int)}.
+ */
+ private NetworkInfo getFilteredNetworkInfo(NetworkStateTracker tracker, int uid) {
+ NetworkInfo info = tracker.getNetworkInfo();
+ if (isNetworkBlocked(tracker, uid)) {
+ // network is blocked; clone and override state
+ info = new NetworkInfo(info);
+ info.setDetailedState(DetailedState.BLOCKED, null, null);
+ }
+ return info;
+ }
+
+ /**
* Return NetworkInfo for the active (i.e., connected) network interface.
* It is assumed that at most one network is active at a time. If more
* than one is active, it is indeterminate which will be returned.
* @return the info for the active network, or {@code null} if none is
* active
*/
+ @Override
public NetworkInfo getActiveNetworkInfo() {
- return getNetworkInfo(mActiveDefaultNetwork);
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
}
+ @Override
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ enforceConnectivityInternalPermission();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+
+ @Override
public NetworkInfo getNetworkInfo(int networkType) {
enforceAccessPermission();
- if (ConnectivityManager.isNetworkTypeValid(networkType)) {
- NetworkStateTracker t = mNetTrackers[networkType];
- if (t != null)
- return t.getNetworkInfo();
- }
- return null;
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(networkType, uid);
}
+ private NetworkInfo getNetworkInfo(int networkType, int uid) {
+ NetworkInfo info = null;
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ info = getFilteredNetworkInfo(tracker, uid);
+ }
+ }
+ return info;
+ }
+
+ @Override
public NetworkInfo[] getAllNetworkInfo() {
enforceAccessPermission();
- NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
- int i = 0;
- for (NetworkStateTracker t : mNetTrackers) {
- if(t != null) result[i++] = t.getNetworkInfo();
+ final int uid = Binder.getCallingUid();
+ final ArrayList<NetworkInfo> result = Lists.newArrayList();
+ synchronized (mRulesLock) {
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ result.add(getFilteredNetworkInfo(tracker, uid));
+ }
+ }
}
- return result;
+ return result.toArray(new NetworkInfo[result.size()]);
+ }
+
+ @Override
+ public boolean isNetworkSupported(int networkType) {
+ enforceAccessPermission();
+ return (isNetworkTypeValid(networkType) && (mNetTrackers[networkType] != null));
}
/**
@@ -614,15 +717,60 @@
* @return the ip properties for the active network, or {@code null} if
* none is active
*/
+ @Override
public LinkProperties getActiveLinkProperties() {
return getLinkProperties(mActiveDefaultNetwork);
}
+ @Override
public LinkProperties getLinkProperties(int networkType) {
enforceAccessPermission();
- if (ConnectivityManager.isNetworkTypeValid(networkType)) {
- NetworkStateTracker t = mNetTrackers[networkType];
- if (t != null) return t.getLinkProperties();
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ return tracker.getLinkProperties();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NetworkState[] getAllNetworkState() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ final ArrayList<NetworkState> result = Lists.newArrayList();
+ synchronized (mRulesLock) {
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ final NetworkInfo info = getFilteredNetworkInfo(tracker, uid);
+ result.add(new NetworkState(
+ info, tracker.getLinkProperties(), tracker.getLinkCapabilities()));
+ }
+ }
+ }
+ return result.toArray(new NetworkState[result.size()]);
+ }
+
+ private NetworkState getNetworkStateUnchecked(int networkType) {
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ return new NetworkState(tracker.getNetworkInfo(), tracker.getLinkProperties(),
+ tracker.getLinkCapabilities());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+ enforceAccessPermission();
+ final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
+ if (state != null) {
+ try {
+ return mPolicyManager.getNetworkQuotaInfo(state);
+ } catch (RemoteException e) {
+ }
}
return null;
}
@@ -688,9 +836,11 @@
}
public void expire() {
- log("ConnectivityService FeatureUser expire(" +
- mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
- (System.currentTimeMillis() - mCreateTime) + " mSec ago");
+ if (VDBG) {
+ log("ConnectivityService FeatureUser expire(" +
+ mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
+ (System.currentTimeMillis() - mCreateTime) + " mSec ago");
+ }
stopUsingNetworkFeature(this, false);
}
@@ -728,15 +878,8 @@
FeatureUser f = new FeatureUser(networkType, feature, binder);
- // TODO - move this into the MobileDataStateTracker
- int usedNetworkType = networkType;
- if(networkType == ConnectivityManager.TYPE_MOBILE) {
- usedNetworkType = convertFeatureToNetworkType(feature);
- if (usedNetworkType < 0) {
- Slog.e(TAG, "Can't match any netTracker!");
- usedNetworkType = networkType;
- }
- }
+ // TODO - move this into individual networktrackers
+ int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
if (mProtectedNetworks.contains(usedNetworkType)) {
enforceConnectivityInternalPermission();
@@ -746,7 +889,6 @@
if (network != null) {
Integer currentPid = new Integer(getCallingPid());
if (usedNetworkType != networkType) {
- NetworkStateTracker radio = mNetTrackers[networkType];
NetworkInfo ni = network.getNetworkInfo();
if (ni.isAvailable() == false) {
@@ -790,7 +932,7 @@
!network.isTeardownRequested()) {
if (ni.isConnected() == true) {
// add the pid-specific dns
- handleDnsConfigurationChange(networkType);
+ handleDnsConfigurationChange(usedNetworkType);
if (DBG) log("special network already active");
return Phone.APN_ALREADY_ACTIVE;
}
@@ -843,7 +985,7 @@
return stopUsingNetworkFeature(u, true);
} else {
// none found!
- if (DBG) log("ignoring stopUsingNetworkFeature - not a live request");
+ if (VDBG) log("ignoring stopUsingNetworkFeature - not a live request");
return 1;
}
}
@@ -892,14 +1034,9 @@
}
}
- // TODO - move to MobileDataStateTracker
- int usedNetworkType = networkType;
- if (networkType == ConnectivityManager.TYPE_MOBILE) {
- usedNetworkType = convertFeatureToNetworkType(feature);
- if (usedNetworkType < 0) {
- usedNetworkType = networkType;
- }
- }
+ // TODO - move to individual network trackers
+ int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+
tracker = mNetTrackers[usedNetworkType];
if (tracker == null) {
if (DBG) log("ignoring - no known tracker for net type " + usedNetworkType);
@@ -971,7 +1108,7 @@
if (tracker == null || !tracker.getNetworkInfo().isConnected() ||
tracker.isTeardownRequested()) {
- if (DBG) {
+ if (VDBG) {
log("requestRouteToHostAddress on down network " +
"(" + networkType + ") - dropped");
}
@@ -1042,13 +1179,13 @@
}
}
if (doAdd) {
- if (DBG) log("Adding " + r + " for interface " + ifaceName);
+ if (VDBG) log("Adding " + r + " for interface " + ifaceName);
mAddedRoutes.add(r);
try {
mNetd.addRoute(ifaceName, r);
} catch (Exception e) {
// never crash - catch them all
- loge("Exception trying to add a route: " + e);
+ if (VDBG) loge("Exception trying to add a route: " + e);
return false;
}
} else {
@@ -1056,51 +1193,22 @@
// we can remove it from the table
mAddedRoutes.remove(r);
if (mAddedRoutes.contains(r) == false) {
- if (DBG) log("Removing " + r + " for interface " + ifaceName);
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
try {
mNetd.removeRoute(ifaceName, r);
} catch (Exception e) {
// never crash - catch them all
- loge("Exception trying to remove a route: " + e);
+ if (VDBG) loge("Exception trying to remove a route: " + e);
return false;
}
} else {
- if (DBG) log("not removing " + r + " as it's still in use");
+ if (VDBG) log("not removing " + r + " as it's still in use");
}
}
return true;
}
/**
- * @see ConnectivityManager#getBackgroundDataSetting()
- */
- public boolean getBackgroundDataSetting() {
- return mBackgroundDataEnabled.get();
- }
-
- /**
- * @see ConnectivityManager#setBackgroundDataSetting(boolean)
- */
- public void setBackgroundDataSetting(boolean allowBackgroundDataUsage) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING,
- "ConnectivityService");
-
- mBackgroundDataEnabled.set(allowBackgroundDataUsage);
-
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA,
- (allowBackgroundDataUsage ? ENABLED : DISABLED), 0));
- }
-
- private void handleSetBackgroundData(boolean enabled) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.BACKGROUND_DATA, enabled ? 1 : 0);
- Intent broadcast = new Intent(
- ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
- mContext.sendBroadcast(broadcast);
- }
-
- /**
* @see ConnectivityManager#getMobileDataEnabled()
*/
public boolean getMobileDataEnabled() {
@@ -1110,7 +1218,7 @@
enforceAccessPermission();
boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.MOBILE_DATA, 1) == 1;
- if (DBG) log("getMobileDataEnabled returning " + retVal);
+ if (VDBG) log("getMobileDataEnabled returning " + retVal);
return retVal;
}
@@ -1130,6 +1238,46 @@
}
}
+ private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ @Override
+ public void onUidRulesChanged(int uid, int uidRules) {
+ // only someone like NPMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ if (LOGD_RULES) {
+ log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+ }
+
+ synchronized (mRulesLock) {
+ // skip update when we've already applied rules
+ final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ if (oldRules == uidRules) return;
+
+ mUidRules.put(uid, uidRules);
+ }
+
+ // TODO: dispatch into NMS to push rules towards kernel module
+ // TODO: notify UID when it has requested targeted updates
+ }
+
+ @Override
+ public void onMeteredIfacesChanged(String[] meteredIfaces) {
+ // only someone like NPMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ if (LOGD_RULES) {
+ log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
+ }
+
+ synchronized (mRulesLock) {
+ mMeteredIfaces.clear();
+ for (String iface : meteredIfaces) {
+ mMeteredIfaces.add(iface);
+ }
+ }
+ }
+ };
+
/**
* @see ConnectivityManager#setMobileDataEnabled(boolean)
*/
@@ -1143,10 +1291,28 @@
private void handleSetMobileData(boolean enabled) {
if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
- if (DBG) {
- Slog.d(TAG, mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
+ if (VDBG) {
+ log(mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
}
- mNetTrackers[ConnectivityManager.TYPE_MOBILE].setDataEnable(enabled);
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE].setUserDataEnable(enabled);
+ }
+ }
+
+ @Override
+ public void setPolicyDataEnable(int networkType, boolean enabled) {
+ // only someone like NPMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_SET_POLICY_DATA_ENABLE, networkType, (enabled ? ENABLED : DISABLED)));
+ }
+
+ private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ tracker.setPolicyDataEnable(enabled);
+ }
}
}
@@ -1260,6 +1426,9 @@
// do this before we broadcast the change
handleConnectivityChange(prevNetType, doReset);
+ final Intent immediateIntent = new Intent(intent);
+ immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+ sendStickyBroadcast(immediateIntent);
sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
/*
* If the failover network is already connected, then immediately send
@@ -1318,11 +1487,13 @@
}
private void sendConnectedBroadcast(NetworkInfo info) {
- sendGeneralBroadcast(info, ConnectivityManager.CONNECTIVITY_ACTION);
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
private void sendConnectedBroadcastDelayed(NetworkInfo info, int delayMs) {
- sendGeneralBroadcastDelayed(info, ConnectivityManager.CONNECTIVITY_ACTION, delayMs);
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+ sendGeneralBroadcastDelayed(info, CONNECTIVITY_ACTION, delayMs);
}
private void sendInetConditionBroadcast(NetworkInfo info) {
@@ -1401,6 +1572,10 @@
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
+
+ final Intent immediateIntent = new Intent(intent);
+ immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+ sendStickyBroadcast(immediateIntent);
sendStickyBroadcast(intent);
/*
* If the failover network is already connected, then immediately send
@@ -1418,8 +1593,7 @@
}
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
if (DBG) {
- log("sendStickyBroadcast: NetworkInfo=" +
- intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO));
+ log("sendStickyBroadcast: action=" + intent.getAction());
}
mContext.sendStickyBroadcast(intent);
@@ -1430,16 +1604,16 @@
if (delayMs <= 0) {
sendStickyBroadcast(intent);
} else {
- if (DBG) log("sendStickyBroadcastDelayed: delayMs=" + delayMs + " intent=" + intent);
+ if (DBG) {
+ log("sendStickyBroadcastDelayed: delayMs=" + delayMs + ", action="
+ + intent.getAction());
+ }
mHandler.sendMessageDelayed(mHandler.obtainMessage(
EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
}
}
void systemReady() {
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- mNetd = INetworkManagementService.Stub.asInterface(b);
-
synchronized(this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
@@ -1467,7 +1641,7 @@
mNetConfigs[type].priority) ||
mNetworkPreference == mActiveDefaultNetwork) {
// don't accept this one
- if (DBG) {
+ if (VDBG) {
log("Not broadcasting CONNECT_ACTION " +
"to torn down network " + info.getTypeName());
}
@@ -1568,9 +1742,11 @@
}
} else {
resetMask = NetworkUtils.RESET_ALL_ADDRESSES;
- log("handleConnectivityChange: interface not not equivalent reset both" +
- " linkProperty[" + netType + "]:" +
- " resetMask=" + resetMask);
+ if (DBG) {
+ log("handleConnectivityChange: interface not not equivalent reset both" +
+ " linkProperty[" + netType + "]:" +
+ " resetMask=" + resetMask);
+ }
}
}
if (mNetConfigs[netType].isDefault()) {
@@ -1585,15 +1761,32 @@
}
}
mCurrentLinkProperties[netType] = newLp;
- updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
+ boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
- if (doReset || resetMask != 0) {
+ if (resetMask != 0 || resetDns) {
LinkProperties linkProperties = mNetTrackers[netType].getLinkProperties();
if (linkProperties != null) {
String iface = linkProperties.getInterfaceName();
if (TextUtils.isEmpty(iface) == false) {
- if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
- NetworkUtils.resetConnections(iface, resetMask);
+ if (resetMask != 0) {
+ if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
+ NetworkUtils.resetConnections(iface, resetMask);
+
+ // Tell VPN the interface is down. It is a temporary
+ // but effective fix to make VPN aware of the change.
+ if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
+ mVpn.interfaceStatusChanged(iface, false);
+ }
+ }
+ if (resetDns) {
+ if (DBG) log("resetting DNS cache for " + iface);
+ try {
+ mNetd.flushInterfaceDnsCache(iface);
+ } catch (Exception e) {
+ // never crash - catch them all
+ loge("Exception resetting dns cache: " + e);
+ }
+ }
}
}
}
@@ -1615,8 +1808,10 @@
* is a noop.
* Uses isLinkDefault to determine if default routes should be set or conversely if
* host routes should be set to the dns servers
+ * returns a boolean indicating the routes changed
*/
- private void updateRoutes(LinkProperties newLp, LinkProperties curLp, boolean isLinkDefault) {
+ private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
+ boolean isLinkDefault) {
Collection<RouteInfo> routesToAdd = null;
CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>();
CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
@@ -1629,6 +1824,8 @@
dnsDiff.added = newLp.getDnses();
}
+ boolean routesChanged = (routeDiff.removed.size() != 0 || routeDiff.added.size() != 0);
+
for (RouteInfo r : routeDiff.removed) {
if (isLinkDefault || ! r.isDefaultRoute()) {
removeRoute(curLp, r);
@@ -1638,20 +1835,25 @@
for (RouteInfo r : routeDiff.added) {
if (isLinkDefault || ! r.isDefaultRoute()) {
addRoute(newLp, r);
+ } else {
+ // many radios add a default route even when we don't want one.
+ // remove the default route unless somebody else has asked for it
+ String ifaceName = newLp.getInterfaceName();
+ if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
+ if (DBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ mNetd.removeRoute(ifaceName, r);
+ } catch (Exception e) {
+ // never crash - catch them all
+ loge("Exception trying to remove a route: " + e);
+ }
+ }
}
}
if (!isLinkDefault) {
// handle DNS routes
- if (routeDiff.removed.size() == 0 && routeDiff.added.size() == 0) {
- // no change in routes, check for change in dns themselves
- for (InetAddress oldDns : dnsDiff.removed) {
- removeRouteToAddress(curLp, oldDns);
- }
- for (InetAddress newDns : dnsDiff.added) {
- addRouteToAddress(newLp, newDns);
- }
- } else {
+ if (routesChanged) {
// routes changed - remove all old dns entries and add new
if (curLp != null) {
for (InetAddress oldDns : curLp.getDnses()) {
@@ -1663,8 +1865,17 @@
addRouteToAddress(newLp, newDns);
}
}
+ } else {
+ // no change in routes, check for change in dns themselves
+ for (InetAddress oldDns : dnsDiff.removed) {
+ removeRouteToAddress(curLp, oldDns);
+ }
+ for (InetAddress newDns : dnsDiff.added) {
+ addRouteToAddress(newLp, newDns);
+ }
}
}
+ return routesChanged;
}
@@ -1678,7 +1889,7 @@
String bufferSizes = SystemProperties.get(key);
if (bufferSizes.length() == 0) {
- loge(key + " not found in system properties. Using defaults");
+ if (VDBG) log(key + " not found in system properties. Using defaults");
// Setting to default values so we won't be stuck to previous values
key = "net.tcp.buffersize.default";
@@ -1687,7 +1898,7 @@
// Set values in kernel
if (bufferSizes.length() != 0) {
- if (DBG) {
+ if (VDBG) {
log("Setting TCP values: [" + bufferSizes
+ "] which comes from [" + key + "]");
}
@@ -1708,12 +1919,12 @@
if (values.length == 6) {
final String prefix = "/sys/kernel/ipv4/tcp_";
- stringToFile(prefix + "rmem_min", values[0]);
- stringToFile(prefix + "rmem_def", values[1]);
- stringToFile(prefix + "rmem_max", values[2]);
- stringToFile(prefix + "wmem_min", values[3]);
- stringToFile(prefix + "wmem_def", values[4]);
- stringToFile(prefix + "wmem_max", values[5]);
+ FileUtils.stringToFile(prefix + "rmem_min", values[0]);
+ FileUtils.stringToFile(prefix + "rmem_def", values[1]);
+ FileUtils.stringToFile(prefix + "rmem_max", values[2]);
+ FileUtils.stringToFile(prefix + "wmem_min", values[3]);
+ FileUtils.stringToFile(prefix + "wmem_def", values[4]);
+ FileUtils.stringToFile(prefix + "wmem_max", values[5]);
} else {
loge("Invalid buffersize string: " + bufferSizes);
}
@@ -1722,23 +1933,6 @@
}
}
- /**
- * Writes string to file. Basically same as "echo -n $string > $filename"
- *
- * @param filename
- * @param string
- * @throws IOException
- */
- private void stringToFile(String filename, String string) throws IOException {
- FileWriter out = new FileWriter(filename);
- try {
- out.write(string);
- } finally {
- out.close();
- }
- }
-
-
/**
* Adjust the per-process dns entries (net.dns<x>.<pid>) based
* on the highest priority active net which this process requested.
@@ -1746,7 +1940,7 @@
*/
private void reassessPidDns(int myPid, boolean doBump)
{
- if (DBG) log("reassessPidDns for pid " + myPid);
+ if (VDBG) log("reassessPidDns for pid " + myPid);
for(int i : mPriorityList) {
if (mNetConfigs[i].isDefault()) {
continue;
@@ -1822,6 +2016,59 @@
mContext.sendBroadcast(intent);
}
+ // Caller must grab mDnsLock.
+ private boolean updateDns(String network, String iface,
+ Collection<InetAddress> dnses, String domains) {
+ boolean changed = false;
+ int last = 0;
+ if (dnses.size() == 0 && mDefaultDns != null) {
+ ++last;
+ String value = mDefaultDns.getHostAddress();
+ if (!value.equals(SystemProperties.get("net.dns1"))) {
+ if (DBG) {
+ loge("no dns provided for " + network + " - using " + value);
+ }
+ changed = true;
+ SystemProperties.set("net.dns1", value);
+ }
+ } else {
+ for (InetAddress dns : dnses) {
+ ++last;
+ String key = "net.dns" + last;
+ String value = dns.getHostAddress();
+ if (!changed && value.equals(SystemProperties.get(key))) {
+ continue;
+ }
+ if (VDBG) {
+ log("adding dns " + value + " for " + network);
+ }
+ changed = true;
+ SystemProperties.set(key, value);
+ }
+ }
+ for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+ String key = "net.dns" + i;
+ if (VDBG) log("erasing " + key);
+ changed = true;
+ SystemProperties.set(key, "");
+ }
+ mNumDnsEntries = last;
+
+ if (changed) {
+ try {
+ mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses));
+ mNetd.setDefaultInterfaceForDns(iface);
+ } catch (Exception e) {
+ loge("exception setting default dns interface: " + e);
+ }
+ }
+ if (!domains.equals(SystemProperties.get("net.dns.search"))) {
+ SystemProperties.set("net.dns.search", domains);
+ changed = true;
+ }
+ return changed;
+ }
+
private void handleDnsConfigurationChange(int netType) {
// add default net's dns entries
NetworkStateTracker nt = mNetTrackers[netType];
@@ -1829,54 +2076,21 @@
LinkProperties p = nt.getLinkProperties();
if (p == null) return;
Collection<InetAddress> dnses = p.getDnses();
- try {
- mNetd.setDnsServersForInterface(p.getInterfaceName(),
- NetworkUtils.makeStrings(dnses));
- } catch (Exception e) {
- Slog.e(TAG, "exception setting dns servers: " + e);
- }
boolean changed = false;
if (mNetConfigs[netType].isDefault()) {
- try {
- mNetd.setDefaultInterfaceForDns(p.getInterfaceName());
- } catch (Exception e) {
- Slog.e(TAG, "exception setting default dns interface: " + e);
- }
- int j = 1;
- if (dnses.size() == 0 && mDefaultDns != null) {
- String dnsString = mDefaultDns.getHostAddress();
- if (!dnsString.equals(SystemProperties.get("net.dns1"))) {
- if (DBG) {
- log("no dns provided - using " + dnsString);
- }
- changed = true;
- SystemProperties.set("net.dns1", dnsString);
- }
- j++;
- } else {
- for (InetAddress dns : dnses) {
- String dnsString = dns.getHostAddress();
- if (!changed && dnsString.equals(SystemProperties.get("net.dns" + j))) {
- j++;
- continue;
- }
- if (DBG) {
- log("adding dns " + dns + " for " +
- nt.getNetworkInfo().getTypeName());
- }
- changed = true;
- SystemProperties.set("net.dns" + j++, dnsString);
+ String network = nt.getNetworkInfo().getTypeName();
+ synchronized (mDnsLock) {
+ if (!mDnsOverridden) {
+ changed = updateDns(network, p.getInterfaceName(), dnses, "");
}
}
- for (int k=j ; k<mNumDnsEntries; k++) {
- if (changed || !TextUtils.isEmpty(SystemProperties.get("net.dns" + k))) {
- if (DBG) log("erasing net.dns" + k);
- changed = true;
- SystemProperties.set("net.dns" + k, "");
- }
- }
- mNumDnsEntries = j;
} else {
+ try {
+ mNetd.setDnsServersForInterface(p.getInterfaceName(),
+ NetworkUtils.makeStrings(dnses));
+ } catch (Exception e) {
+ loge("exception setting dns servers: " + e);
+ }
// set per-pid dns for attached secondary nets
List pids = mNetRequestersPids[netType];
for (int y=0; y< pids.size(); y++) {
@@ -2059,12 +2273,6 @@
handleSetNetworkPreference(preference);
break;
}
- case EVENT_SET_BACKGROUND_DATA:
- {
- boolean enabled = (msg.arg1 == ENABLED);
- handleSetBackgroundData(enabled);
- break;
- }
case EVENT_SET_MOBILE_DATA:
{
boolean enabled = (msg.arg1 == ENABLED);
@@ -2082,13 +2290,24 @@
handleSetDependencyMet(msg.arg2, met);
break;
}
+ case EVENT_RESTORE_DNS:
+ {
+ if (mActiveDefaultNetwork != -1) {
+ handleDnsConfigurationChange(mActiveDefaultNetwork);
+ }
+ break;
+ }
case EVENT_SEND_STICKY_BROADCAST_INTENT:
{
Intent intent = (Intent)msg.obj;
- log("EVENT_SEND_STICKY_BROADCAST_INTENT: sendStickyBroadcast intent=" + intent);
sendStickyBroadcast(intent);
break;
}
+ case EVENT_SET_POLICY_DATA_ENABLE: {
+ final int networkType = msg.arg1;
+ final boolean enabled = msg.arg2 == ENABLED;
+ handleSetPolicyDataEnable(networkType, enabled);
+ }
}
}
}
@@ -2154,6 +2373,15 @@
}
}
+ public int setUsbTethering(boolean enable) {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.setUsbTethering(enable);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
// TODO - move iface listing, queries, etc to new module
// javadoc from interface
public String[] getTetherableIfaces() {
@@ -2203,7 +2431,7 @@
// 100 percent is full good, 0 is full bad.
public void reportInetCondition(int networkType, int percentage) {
- if (DBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
+ if (VDBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.STATUS_BAR,
"ConnectivityService");
@@ -2240,7 +2468,7 @@
mDefaultInetCondition = condition;
int delay;
if (mInetConditionChangeInFlight == false) {
- if (DBG) log("starting a change hold");
+ if (VDBG) log("starting a change hold");
// setup a new hold to debounce this
if (mDefaultInetCondition > 50) {
delay = Settings.Secure.getInt(mContext.getContentResolver(),
@@ -2255,12 +2483,12 @@
} else {
// we've set the new condition, when this hold ends that will get
// picked up
- if (DBG) log("currently in hold - not setting new end evt");
+ if (VDBG) log("currently in hold - not setting new end evt");
}
}
private void handleInetConditionHoldEnd(int netType, int sequence) {
- if (DBG) {
+ if (VDBG) {
log("Inet hold end, net=" + netType +
", condition =" + mDefaultInetCondition +
", published condition =" + mDefaultInetConditionPublished);
@@ -2361,7 +2589,7 @@
mDefaultProxy = null;
}
}
- if (DBG) log("changing default proxy to " + proxy);
+ if (VDBG) log("changing default proxy to " + proxy);
if ((proxy == null && mGlobalProxy == null) || proxy.equals(mGlobalProxy)) return;
if (mGlobalProxy != null) return;
sendProxyBroadcast(proxy);
@@ -2388,7 +2616,7 @@
private void sendProxyBroadcast(ProxyProperties proxy) {
if (proxy == null) proxy = new ProxyProperties("", 0, "");
- log("sending Proxy Broadcast for " + proxy);
+ if (DBG) log("sending Proxy Broadcast for " + proxy);
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -2424,24 +2652,185 @@
private void loge(String s) {
Slog.e(TAG, s);
}
- int convertFeatureToNetworkType(String feature){
- int networkType = -1;
- if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
- networkType = ConnectivityManager.TYPE_MOBILE_MMS;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
- networkType = ConnectivityManager.TYPE_MOBILE_SUPL;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) ||
- TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
- networkType = ConnectivityManager.TYPE_MOBILE_DUN;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
- networkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) {
- networkType = ConnectivityManager.TYPE_MOBILE_FOTA;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) {
- networkType = ConnectivityManager.TYPE_MOBILE_IMS;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) {
- networkType = ConnectivityManager.TYPE_MOBILE_CBS;
+
+ int convertFeatureToNetworkType(int networkType, String feature) {
+ int usedNetworkType = networkType;
+
+ if(networkType == ConnectivityManager.TYPE_MOBILE) {
+ if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) ||
+ TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_FOTA;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_IMS;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_CBS;
+ } else {
+ Slog.e(TAG, "Can't match any mobile netTracker!");
+ }
+ } else if (networkType == ConnectivityManager.TYPE_WIFI) {
+ if (TextUtils.equals(feature, "p2p")) {
+ usedNetworkType = ConnectivityManager.TYPE_WIFI_P2P;
+ } else {
+ Slog.e(TAG, "Can't match any wifi netTracker!");
+ }
+ } else {
+ Slog.e(TAG, "Unexpected network type");
}
- return networkType;
+ return usedNetworkType;
+ }
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+
+ /**
+ * Protect a socket from VPN routing rules. This method is used by
+ * VpnBuilder and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public boolean protectVpn(ParcelFileDescriptor socket) {
+ try {
+ int type = mActiveDefaultNetwork;
+ if (ConnectivityManager.isNetworkTypeValid(type)) {
+ mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName());
+ return true;
+ }
+ } catch (Exception e) {
+ // ignore
+ } finally {
+ try {
+ socket.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Prepare for a VPN application. This method is used by VpnDialogs
+ * and not available in ConnectivityManager. Permissions are checked
+ * in Vpn class.
+ * @hide
+ */
+ @Override
+ public boolean prepareVpn(String oldPackage, String newPackage) {
+ return mVpn.prepare(oldPackage, newPackage);
+ }
+
+ /**
+ * Configure a TUN interface and return its file descriptor. Parameters
+ * are encoded and opaque to this class. This method is used by VpnBuilder
+ * and not available in ConnectivityManager. Permissions are checked in
+ * Vpn class.
+ * @hide
+ */
+ @Override
+ public ParcelFileDescriptor establishVpn(VpnConfig config) {
+ return mVpn.establish(config);
+ }
+
+ /**
+ * Start legacy VPN and return an intent to VpnDialogs. This method is
+ * used by VpnSettings and not available in ConnectivityManager.
+ * Permissions are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
+ mVpn.startLegacyVpn(config, racoon, mtpd);
+ }
+
+ /**
+ * Return the information of the ongoing legacy VPN. This method is used
+ * by VpnSettings and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public LegacyVpnInfo getLegacyVpnInfo() {
+ return mVpn.getLegacyVpnInfo();
+ }
+
+ /**
+ * Callback for VPN subsystem. Currently VPN is not adapted to the service
+ * through NetworkStateTracker since it works differently. For example, it
+ * needs to override DNS servers but never takes the default routes. It
+ * relies on another data network, and it could keep existing connections
+ * alive after reconnecting, switching between networks, or even resuming
+ * from deep sleep. Calls from applications should be done synchronously
+ * to avoid race conditions. As these are all hidden APIs, refactoring can
+ * be done whenever a better abstraction is developed.
+ */
+ public class VpnCallback {
+
+ private VpnCallback() {
+ }
+
+ public void override(List<String> dnsServers, List<String> searchDomains) {
+ if (dnsServers == null) {
+ restore();
+ return;
+ }
+
+ // Convert DNS servers into addresses.
+ List<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (String address : dnsServers) {
+ // Double check the addresses and remove invalid ones.
+ try {
+ addresses.add(InetAddress.parseNumericAddress(address));
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ if (addresses.isEmpty()) {
+ restore();
+ return;
+ }
+
+ // Concatenate search domains into a string.
+ StringBuilder buffer = new StringBuilder();
+ if (searchDomains != null) {
+ for (String domain : searchDomains) {
+ buffer.append(domain).append(' ');
+ }
+ }
+ String domains = buffer.toString().trim();
+
+ // Apply DNS changes.
+ boolean changed = false;
+ synchronized (mDnsLock) {
+ changed = updateDns("VPN", "VPN", addresses, domains);
+ mDnsOverridden = true;
+ }
+ if (changed) {
+ bumpDns();
+ }
+
+ // TODO: temporarily remove http proxy?
+ }
+
+ public void restore() {
+ synchronized (mDnsLock) {
+ if (!mDnsOverridden) {
+ return;
+ }
+ mDnsOverridden = false;
+ }
+ mHandler.sendEmptyMessage(EVENT_RESTORE_DNS);
+ }
}
}
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_typical b/services/tests/servicestests/res/raw/xt_qtaguid_typical
new file mode 100644
index 0000000..8df4b1b
--- /dev/null
+++ b/services/tests/servicestests/res/raw/xt_qtaguid_typical
@@ -0,0 +1,32 @@
+idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
+2 wlan0 0x0 0 14615 4270
+3 wlan0 0x0 1000 5175 915
+4 wlan0 0x0 1021 3381 903
+5 wlan0 0x0 10004 333821 53558
+6 wlan0 0x0 10010 4888 37363
+7 wlan0 0x0 10013 52 104
+8 wlan0 0x74182ada00000000 10004 18725 1066
+9 rmnet0 0x0 0 301274 30244
+10 rmnet0 0x0 1000 304 441
+11 rmnet0 0x0 1013 2880 2272
+12 rmnet0 0x0 1021 31407 8430
+13 rmnet0 0x0 10003 32665 3814
+14 rmnet0 0x0 10004 2373141 420112
+15 rmnet0 0x0 10010 870370 1111727
+16 rmnet0 0x0 10013 240 240
+17 rmnet0 0x0 10016 16703 13512
+18 rmnet0 0x0 10017 3990 3269
+19 rmnet0 0x0 10018 474504 14516062
+20 rmnet0 0x0 10019 782804 71077
+21 rmnet0 0x0 10022 70671 49684
+22 rmnet0 0x0 10029 5785354 397159
+23 rmnet0 0x0 10033 2102 1686
+24 rmnet0 0x0 10034 15495464 227694
+25 rmnet0 0x0 10037 31184994 684122
+26 rmnet0 0x0 10051 298687 113485
+27 rmnet0 0x0 10056 29504 20669
+28 rmnet0 0x0 10069 683 596
+29 rmnet0 0x0 10072 34051 12453
+30 rmnet0 0x0 10077 7025393 213866
+31 rmnet0 0x0 10081 354 1178
+32 rmnet0 0x74182ada00000000 10037 28507378 437004
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..2a25866
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static com.android.server.NetworkManagementSocketTagger.tagToKernel;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.frameworks.servicestests.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@LargeTest
+public class NetworkManagementServiceTest extends AndroidTestCase {
+ private File mTestProc;
+ private NetworkManagementService mService;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mTestProc = new File(getContext().getFilesDir(), "proc");
+ if (mTestProc.exists()) {
+ IoUtils.deleteContents(mTestProc);
+ }
+
+ mService = NetworkManagementService.createForTest(mContext, mTestProc, true);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mService = null;
+
+ if (mTestProc.exists()) {
+ IoUtils.deleteContents(mTestProc);
+ }
+
+ super.tearDown();
+ }
+
+ public void testNetworkStatsDetail() throws Exception {
+ stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+ final NetworkStats stats = mService.getNetworkStatsDetail();
+ assertEquals(31, stats.size());
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0, 14615L, 4270L);
+ assertStatsEntry(stats, "wlan0", 10004, SET_DEFAULT, 0, 333821L, 53558L);
+ assertStatsEntry(stats, "wlan0", 10004, SET_DEFAULT, 1947740890, 18725L, 1066L);
+ assertStatsEntry(stats, "rmnet0", 10037, SET_DEFAULT, 0, 31184994L, 684122L);
+ assertStatsEntry(stats, "rmnet0", 10037, SET_DEFAULT, 1947740890, 28507378L, 437004L);
+ }
+
+ public void testNetworkStatsDetailExtended() throws Exception {
+ stageFile(R.raw.xt_qtaguid_extended, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+ final NetworkStats stats = mService.getNetworkStatsDetail();
+ assertEquals(2, stats.size());
+ assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0, 1024L, 2048L);
+ assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0xF00D, 512L, 512L);
+ }
+
+ public void testNetworkStatsSummary() throws Exception {
+ stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
+
+ final NetworkStats stats = mService.getNetworkStatsSummary();
+ assertEquals(6, stats.size());
+ assertStatsEntry(stats, "lo", UID_ALL, SET_DEFAULT, TAG_NONE, 8308L, 8308L);
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L, 489339L);
+ assertStatsEntry(stats, "ifb0", UID_ALL, SET_DEFAULT, TAG_NONE, 52454L, 0L);
+ assertStatsEntry(stats, "ifb1", UID_ALL, SET_DEFAULT, TAG_NONE, 52454L, 0L);
+ assertStatsEntry(stats, "sit0", UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L);
+ assertStatsEntry(stats, "ip6tnl0", UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L);
+ }
+
+ public void testNetworkStatsSummaryDown() throws Exception {
+ stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
+ stageLong(1L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/active"));
+ stageLong(1024L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/rx_bytes"));
+ stageLong(128L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/rx_packets"));
+ stageLong(2048L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/tx_bytes"));
+ stageLong(256L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/tx_packets"));
+
+ final NetworkStats stats = mService.getNetworkStatsSummary();
+ assertEquals(7, stats.size());
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L, 489339L);
+ assertStatsEntry(stats, "wlan0", UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 2048L);
+ }
+
+ public void testNetworkStatsCombined() throws Exception {
+ stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
+ stageLong(1L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/active"));
+ stageLong(10L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_bytes"));
+ stageLong(20L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_packets"));
+ stageLong(30L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_bytes"));
+ stageLong(40L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_packets"));
+
+ final NetworkStats stats = mService.getNetworkStatsSummary();
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L + 10L,
+ 2205L + 20L, 489339L + 30L, 2237L + 40L);
+ }
+
+ public void testNetworkStatsCombinedInactive() throws Exception {
+ stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
+ stageLong(0L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/active"));
+ stageLong(10L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_bytes"));
+ stageLong(20L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_packets"));
+ stageLong(30L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_bytes"));
+ stageLong(40L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_packets"));
+
+ final NetworkStats stats = mService.getNetworkStatsSummary();
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 10L, 20L, 30L, 40L);
+ }
+
+ public void testKernelTags() throws Exception {
+ assertEquals("0", tagToKernel(0x0));
+ assertEquals("214748364800", tagToKernel(0x32));
+ assertEquals("9223372032559808512", tagToKernel(Integer.MAX_VALUE));
+ assertEquals("0", tagToKernel(Integer.MIN_VALUE));
+ assertEquals("9223369837831520256", tagToKernel(Integer.MIN_VALUE - 512));
+
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(0x32, kernelToTag("0x0000003200000000"));
+ assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
+ }
+
+ public void testNetworkStatsWithSet() throws Exception {
+ stageFile(R.raw.xt_qtaguid_typical_with_set, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+ final NetworkStats stats = mService.getNetworkStatsDetail();
+ assertEquals(12, stats.size());
+ assertStatsEntry(stats, "rmnet0", 1000, SET_DEFAULT, 0, 278102L, 253L, 10487L, 182L);
+ assertStatsEntry(stats, "rmnet0", 1000, SET_FOREGROUND, 0, 26033L, 30L, 1401L, 26L);
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void stageLong(long value, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ FileWriter out = null;
+ try {
+ out = new FileWriter(file);
+ out.write(Long.toString(value));
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long txBytes) {
+ final int i = stats.findIndex(iface, uid, set, tag);
+ final NetworkStats.Entry entry = stats.getValues(i, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final int i = stats.findIndex(iface, uid, set, tag);
+ final NetworkStats.Entry entry = stats.getValues(i, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
new file mode 100644
index 0000000..e7f1d9a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -0,0 +1,862 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifi;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkState;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.INetworkManagementService;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.TrustedTime;
+
+import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+
+import java.io.File;
+
+import libcore.io.IoUtils;
+
+/**
+ * Tests for {@link NetworkStatsService}.
+ */
+@LargeTest
+public class NetworkStatsServiceTest extends AndroidTestCase {
+ private static final String TAG = "NetworkStatsServiceTest";
+
+ private static final String TEST_IFACE = "test0";
+ private static final long TEST_START = 1194220800000L;
+
+ private static final String IMSI_1 = "310004";
+ private static final String IMSI_2 = "310260";
+
+ private static NetworkTemplate sTemplateWifi = buildTemplateWifi();
+ private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
+ private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+
+ private static final int UID_RED = 1001;
+ private static final int UID_BLUE = 1002;
+ private static final int UID_GREEN = 1003;
+
+ private long mElapsedRealtime;
+
+ private BroadcastInterceptingContext mServiceContext;
+ private File mStatsDir;
+
+ private INetworkManagementService mNetManager;
+ private IAlarmManager mAlarmManager;
+ private TrustedTime mTime;
+ private NetworkStatsSettings mSettings;
+ private IConnectivityManager mConnManager;
+
+ private NetworkStatsService mService;
+ private INetworkManagementEventObserver mNetworkObserver;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mServiceContext = new BroadcastInterceptingContext(getContext());
+ mStatsDir = getContext().getFilesDir();
+ if (mStatsDir.exists()) {
+ IoUtils.deleteContents(mStatsDir);
+ }
+
+ mNetManager = createMock(INetworkManagementService.class);
+ mAlarmManager = createMock(IAlarmManager.class);
+ mTime = createMock(TrustedTime.class);
+ mSettings = createMock(NetworkStatsSettings.class);
+ mConnManager = createMock(IConnectivityManager.class);
+
+ mService = new NetworkStatsService(
+ mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir, mSettings);
+ mService.bindConnectivityManager(mConnManager);
+
+ mElapsedRealtime = 0L;
+
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+
+ // catch INetworkManagementEventObserver during systemReady()
+ final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
+ INetworkManagementEventObserver>();
+ mNetManager.registerObserver(capture(networkObserver));
+ expectLastCall().atLeastOnce();
+
+ replay();
+ mService.systemReady();
+ verifyAndReset();
+
+ mNetworkObserver = networkObserver.getValue();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ for (File file : mStatsDir.listFiles()) {
+ file.delete();
+ }
+
+ mServiceContext = null;
+ mStatsDir = null;
+
+ mNetManager = null;
+ mAlarmManager = null;
+ mTime = null;
+ mSettings = null;
+ mConnManager = null;
+
+ mService = null;
+
+ super.tearDown();
+ }
+
+ public void testNetworkStatsWifi() throws Exception {
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ verifyAndReset();
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
+ verifyAndReset();
+
+ // and bump forward again, with counters going higher. this is
+ // important, since polling should correctly subtract last snapshot.
+ incrementCurrentTime(DAY_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4096L, 4L, 8192L, 8L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
+ verifyAndReset();
+
+ }
+
+ public void testStatsRebootPersist() throws Exception {
+ assertStatsFilesExist(false);
+
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ verifyAndReset();
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.setUidForeground(UID_RED, false);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
+ mService.setUidForeground(UID_RED, true);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, 512L, 4L, 256L, 2L, 4);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, 512L, 4L, 256L, 2L, 6);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
+ verifyAndReset();
+
+ // graceful shutdown system, which should trigger persist of stats, and
+ // clear any values in memory.
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+
+ // talk with zombie service to assert stats have gone; and assert that
+ // we persisted them to file.
+ expectDefaultSettings();
+ replay();
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ verifyAndReset();
+
+ assertStatsFilesExist(true);
+
+ // boot through serviceReady() again
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+
+ // catch INetworkManagementEventObserver during systemReady()
+ final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
+ INetworkManagementEventObserver>();
+ mNetManager.registerObserver(capture(networkObserver));
+ expectLastCall().atLeastOnce();
+
+ replay();
+ mService.systemReady();
+
+ mNetworkObserver = networkObserver.getValue();
+
+ // after systemReady(), we should have historical stats loaded again
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, 512L, 4L, 256L, 2L, 4);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, 512L, 4L, 256L, 2L, 6);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
+ verifyAndReset();
+
+ }
+
+ public void testStatsBucketResize() throws Exception {
+ NetworkStatsHistory history = null;
+
+ assertStatsFilesExist(false);
+
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(2 * HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
+ assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
+ assertEquals(2, history.size());
+ verifyAndReset();
+
+ // now change bucket duration setting and trigger another poll with
+ // exact same values, which should resize existing buckets.
+ expectCurrentTime();
+ expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify identical stats, but spread across 4 buckets now
+ history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
+ assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
+ assertEquals(4, history.size());
+ verifyAndReset();
+
+ }
+
+ public void testUidStatsAcrossNetworks() throws Exception {
+ // pretend first mobile network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic on first network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 10);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
+ assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
+ verifyAndReset();
+
+ // now switch networks; this also tests that we're okay with interfaces
+ // disappearing, to verify we don't count backwards.
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_2));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ verifyAndReset();
+
+ // create traffic on second network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 128L, 1L, 1024L, 8L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify original history still intact
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
+ assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
+
+ // and verify new history also recorded under different template, which
+ // verifies that we didn't cross the streams.
+ assertNetworkTotal(sTemplateImsi2, 128L, 1L, 1024L, 8L, 0);
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1L, 1024L, 8L, 10);
+ verifyAndReset();
+
+ }
+
+ public void testUidRemovedIsMoved() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 10);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
+ verifyAndReset();
+
+ // now pretend two UIDs are uninstalled, which should migrate stats to
+ // special "removed" bucket.
+ expectDefaultSettings();
+ replay();
+ final Intent intent = new Intent(ACTION_UID_REMOVED);
+ intent.putExtra(EXTRA_UID, UID_BLUE);
+ mServiceContext.sendBroadcast(intent);
+ intent.putExtra(EXTRA_UID, UID_RED);
+ mServiceContext.sendBroadcast(intent);
+
+ // existing uid and total should remain unchanged; but removed UID
+ // should be gone completely.
+ assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
+ assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 259L, 528L, 33L, 10);
+ verifyAndReset();
+
+ }
+
+ public void testUid3g4gCombinedByTemplate() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 5);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
+ verifyAndReset();
+
+ // now switch over to 4g network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile4gState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ verifyAndReset();
+
+ // create traffic on second network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify that ALL_MOBILE template combines both
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
+
+ verifyAndReset();
+
+ }
+
+ public void testSummaryForAllUid() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic for two apps
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 8L, 512L, 4L, 0);
+ verifyAndReset();
+
+ // now create more traffic in next hour, but only for one app
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // first verify entire history present
+ NetworkStats stats = mService.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(3, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 1);
+ assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0);
+
+ // now verify that recent history only contains one uid
+ final long currentTime = currentTimeMillis();
+ stats = mService.getSummaryForAllUid(
+ sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
+ assertEquals(1, stats.size());
+ assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0);
+
+ verifyAndReset();
+ }
+
+ public void testForegroundBackground() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some initial traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+ verifyAndReset();
+
+ // now switch to foreground
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
+ expectNetworkStatsPoll();
+
+ mService.setUidForeground(UID_RED, true);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // test that we combined correctly
+ assertUidTotal(sTemplateWifi, UID_RED, 160L, 4L, 160L, 4L, 2);
+
+ // verify entire history present
+ final NetworkStats stats = mService.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(4, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 1);
+
+ verifyAndReset();
+ }
+
+ private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, int operations) {
+ final NetworkStatsHistory history = mService.getHistoryForNetwork(template, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, int operations) {
+ assertUidTotal(template, uid, SET_ALL, rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ private void assertUidTotal(NetworkTemplate template, int uid, int set, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) {
+ final NetworkStatsHistory history = mService.getHistoryForUid(
+ template, uid, set, TAG_NONE, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private void expectSystemReady() throws Exception {
+ mAlarmManager.remove(isA(PendingIntent.class));
+ expectLastCall().anyTimes();
+
+ mAlarmManager.setInexactRepeating(
+ eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class));
+ expectLastCall().atLeastOnce();
+
+ mNetManager.setGlobalAlert(anyLong());
+ expectLastCall().atLeastOnce();
+ }
+
+ private void expectNetworkState(NetworkState... state) throws Exception {
+ expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+ }
+
+ private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+ expect(mNetManager.getNetworkStatsSummary()).andReturn(summary).atLeastOnce();
+ }
+
+ private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
+ expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce();
+ }
+
+ private void expectDefaultSettings() throws Exception {
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ }
+
+ private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
+ throws Exception {
+ expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
+ expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
+ expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
+ expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
+ expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
+ expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
+ expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
+ expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
+ }
+
+ private void expectCurrentTime() throws Exception {
+ expect(mTime.forceRefresh()).andReturn(false).anyTimes();
+ expect(mTime.hasCache()).andReturn(true).anyTimes();
+ expect(mTime.currentTimeMillis()).andReturn(currentTimeMillis()).anyTimes();
+ expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
+ expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
+ }
+
+ private void expectNetworkStatsPoll() throws Exception {
+ mNetManager.setGlobalAlert(anyLong());
+ expectLastCall().anyTimes();
+ }
+
+ private void assertStatsFilesExist(boolean exist) {
+ final File networkFile = new File(mStatsDir, "netstats.bin");
+ final File uidFile = new File(mStatsDir, "netstats_uid.bin");
+ if (exist) {
+ assertTrue(networkFile.exists());
+ assertTrue(uidFile.exists());
+ } else {
+ assertFalse(networkFile.exists());
+ assertFalse(uidFile.exists());
+ }
+ }
+
+ private static void assertValues(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) {
+ final int i = stats.findIndex(iface, uid, set, tag);
+ final NetworkStats.Entry entry = stats.getValues(i, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static NetworkState buildWifiState() {
+ final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TEST_IFACE);
+ return new NetworkState(info, prop, null);
+ }
+
+ private static NetworkState buildMobile3gState(String subscriberId) {
+ final NetworkInfo info = new NetworkInfo(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TEST_IFACE);
+ return new NetworkState(info, prop, null, subscriberId);
+ }
+
+ private static NetworkState buildMobile4gState() {
+ final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TEST_IFACE);
+ return new NetworkState(info, prop, null);
+ }
+
+ private NetworkStats buildEmptyStats() {
+ return new NetworkStats(getElapsedRealtime(), 0);
+ }
+
+ private long getElapsedRealtime() {
+ return mElapsedRealtime;
+ }
+
+ private long startTimeMillis() {
+ return TEST_START;
+ }
+
+ private long currentTimeMillis() {
+ return startTimeMillis() + mElapsedRealtime;
+ }
+
+ private void incrementCurrentTime(long duration) {
+ mElapsedRealtime += duration;
+ }
+
+ private void replay() {
+ EasyMock.replay(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+ }
+
+ private void verifyAndReset() {
+ EasyMock.verify(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+ EasyMock.reset(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+ }
+}