Merge "Collect historical network stats."

This commit is contained in:
Jeff Sharkey
2011-06-01 17:54:37 -07:00
committed by Android (Google) Code Review
15 changed files with 1023 additions and 180 deletions

View File

@@ -112,6 +112,7 @@ LOCAL_SRC_FILES += \
core/java/android/net/IThrottleManager.aidl \
core/java/android/net/INetworkPolicyListener.aidl \
core/java/android/net/INetworkPolicyManager.aidl \
core/java/android/net/INetworkStatsService.aidl \
core/java/android/nfc/ILlcpConnectionlessSocket.aidl \
core/java/android/nfc/ILlcpServiceSocket.aidl \
core/java/android/nfc/ILlcpSocket.aidl \
@@ -124,7 +125,6 @@ LOCAL_SRC_FILES += \
core/java/android/os/IHardwareService.aidl \
core/java/android/os/IMessenger.aidl \
core/java/android/os/INetworkManagementService.aidl \
core/java/android/os/INetStatService.aidl \
core/java/android/os/IPermissionController.aidl \
core/java/android/os/IPowerManager.aidl \
core/java/android/os/IRemoteCallback.aidl \

View File

@@ -98,6 +98,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libstagefright_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************

View File

@@ -1544,6 +1544,8 @@ public abstract class Context {
*/
public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
/** {@hide} */
public static final String NETWORK_STATS_SERVICE = "netstats";
/** {@hide} */
public static final String NETWORK_POLICY_SERVICE = "netpolicy";

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* 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.
@@ -14,22 +14,14 @@
* limitations under the License.
*/
package android.os;
package android.net;
import android.net.NetworkStatsHistory;
/** {@hide} */
interface INetworkStatsService {
NetworkStatsHistory[] getNetworkStatsSummary(int networkType);
NetworkStatsHistory getNetworkStatsUid(int uid);
/**
* Retrieves packet and byte counts for the phone data interface,
* and for all interfaces.
* Used for the data activity icon and the phone status in Settings.
*
* {@hide}
*/
interface INetStatService {
long getMobileTxPackets();
long getMobileRxPackets();
long getMobileTxBytes();
long getMobileRxBytes();
long getTotalTxPackets();
long getTotalRxPackets();
long getTotalTxBytes();
long getTotalRxBytes();
}

View File

@@ -22,19 +22,22 @@ import android.os.SystemClock;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.HashSet;
/**
* Collection of network statistics. Can contain summary details across all
* interfaces, or details with per-UID granularity. Designed to parcel quickly
* across process boundaries.
* Collection of active network statistics. Can contain summary details across
* all interfaces, or details with per-UID granularity. Internally stores data
* as a large table, closely matching {@code /proc/} data format. This structure
* optimizes for rapid in-memory comparison, but consider using
* {@link NetworkStatsHistory} when persisting.
*
* @hide
*/
public class NetworkStats implements Parcelable {
/** {@link #iface} value when entry is summarized over all interfaces. */
/** {@link #iface} value when interface details unavailable. */
public static final String IFACE_ALL = null;
/** {@link #uid} value when entry is summarized over all UIDs. */
public static final int UID_ALL = 0;
/** {@link #uid} value when UID details unavailable. */
public static final int UID_ALL = -1;
// NOTE: data should only be accounted for once in this structure; if data
// is broken out, the summarized version should not be included.
@@ -49,7 +52,7 @@ public class NetworkStats implements Parcelable {
public final long[] rx;
public final long[] tx;
// TODO: add fg/bg stats and tag granularity
// TODO: add fg/bg stats once reported by kernel
private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) {
this.elapsedRealtime = elapsedRealtime;
@@ -119,16 +122,36 @@ public class NetworkStats implements Parcelable {
return -1;
}
/**
* Return list of unique interfaces known by this data structure.
*/
public String[] getKnownIfaces() {
final HashSet<String> ifaces = new HashSet<String>();
for (String iface : this.iface) {
if (iface != IFACE_ALL) {
ifaces.add(iface);
}
}
return ifaces.toArray(new String[ifaces.size()]);
}
/**
* Subtract the given {@link NetworkStats}, effectively leaving the delta
* between two snapshots in time. Assumes that statistics rows collect over
* time, and that none of them have disappeared.
*
* @param enforceMonotonic Validate that incoming value is strictly
* monotonic compared to this object.
*/
public NetworkStats subtract(NetworkStats value) {
// result will have our rows, but no meaningful timestamp
final int length = length();
final NetworkStats.Builder result = new NetworkStats.Builder(-1, length);
public NetworkStats subtract(NetworkStats value, boolean enforceMonotonic) {
final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
if (enforceMonotonic && deltaRealtime < 0) {
throw new IllegalArgumentException("found non-monotonic realtime");
}
// result will have our rows, and elapsed time between snapshots
final int length = length();
final NetworkStats.Builder result = new NetworkStats.Builder(deltaRealtime, length);
for (int i = 0; i < length; i++) {
final String iface = this.iface[i];
final int uid = this.uid[i];
@@ -142,6 +165,9 @@ public class NetworkStats implements Parcelable {
// existing row, subtract remote value
final long rx = this.rx[i] - value.rx[j];
final long tx = this.tx[i] - value.tx[j];
if (enforceMonotonic && (rx < 0 || tx < 0)) {
throw new IllegalArgumentException("found non-monotonic values");
}
result.addEntry(iface, uid, rx, tx);
}
}

View File

@@ -0,0 +1,19 @@
/**
* 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;
parcelable NetworkStatsHistory;

View File

@@ -0,0 +1,285 @@
/*
* 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;
import java.io.CharArrayWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* Collection of historical network statistics, recorded into equally-sized
* "buckets" in time. Internally it stores data in {@code long} series for more
* efficient persistence.
* <p>
* Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
* {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
* sorted at all times.
*
* @hide
*/
public class NetworkStatsHistory implements Parcelable {
private static final int VERSION = 1;
/** {@link #uid} value when UID details unavailable. */
public static final int UID_ALL = -1;
// TODO: teach about zigzag encoding to use less disk space
// TODO: teach how to convert between bucket sizes
public final int networkType;
public final String identity;
public final int uid;
public final long bucketDuration;
int bucketCount;
long[] bucketStart;
long[] rx;
long[] tx;
public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) {
this.networkType = networkType;
this.identity = identity;
this.uid = uid;
this.bucketDuration = bucketDuration;
bucketStart = new long[0];
rx = new long[0];
tx = new long[0];
bucketCount = bucketStart.length;
}
public NetworkStatsHistory(Parcel in) {
networkType = in.readInt();
identity = in.readString();
uid = in.readInt();
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
rx = in.createLongArray();
tx = in.createLongArray();
bucketCount = bucketStart.length;
}
/** {@inheritDoc} */
public void writeToParcel(Parcel out, int flags) {
out.writeInt(networkType);
out.writeString(identity);
out.writeInt(uid);
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rx, bucketCount);
writeLongArray(out, tx, bucketCount);
}
public NetworkStatsHistory(DataInputStream in) throws IOException {
final int version = in.readInt();
networkType = in.readInt();
identity = in.readUTF();
uid = in.readInt();
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
rx = readLongArray(in);
tx = readLongArray(in);
bucketCount = bucketStart.length;
}
public void writeToStream(DataOutputStream out) throws IOException {
out.writeInt(VERSION);
out.writeInt(networkType);
out.writeUTF(identity);
out.writeInt(uid);
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rx, bucketCount);
writeLongArray(out, tx, bucketCount);
}
/** {@inheritDoc} */
public int describeContents() {
return 0;
}
/**
* Record that data traffic occurred in the given time range. Will
* distribute across internal buckets, creating new buckets as needed.
*/
public void recordData(long start, long end, long rx, long tx) {
// create any buckets needed by this range
ensureBuckets(start, end);
// distribute data usage into buckets
final long duration = end - start;
for (int i = bucketCount - 1; i >= 0; i--) {
final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration;
// bucket is older than record; we're finished
if (curEnd < start) break;
// bucket is newer than record; keep looking
if (curStart > end) continue;
final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
if (overlap > 0) {
this.rx[i] += rx * overlap / duration;
this.tx[i] += tx * overlap / duration;
}
}
}
/**
* Ensure that buckets exist for given time range, creating as needed.
*/
private void ensureBuckets(long start, long end) {
// normalize incoming range to bucket boundaries
start -= start % bucketDuration;
end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
for (long now = start; now < end; now += bucketDuration) {
// try finding existing bucket
final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
if (index < 0) {
// bucket missing, create and insert
insertBucket(~index, now);
}
}
}
/**
* Insert new bucket at requested index and starting time.
*/
private void insertBucket(int index, long start) {
// create more buckets when needed
if (bucketCount + 1 > bucketStart.length) {
final int newLength = bucketStart.length + 10;
bucketStart = Arrays.copyOf(bucketStart, newLength);
rx = Arrays.copyOf(rx, newLength);
tx = Arrays.copyOf(tx, newLength);
}
// create gap when inserting bucket in middle
if (index < bucketCount) {
final int dstPos = index + 1;
final int length = bucketCount - index;
System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
System.arraycopy(rx, index, rx, dstPos, length);
System.arraycopy(tx, index, tx, dstPos, length);
}
bucketStart[index] = start;
rx[index] = 0;
tx[index] = 0;
bucketCount++;
}
/**
* Remove buckets older than requested cutoff.
*/
public void removeBucketsBefore(long cutoff) {
int i;
for (i = 0; i < bucketCount; i++) {
final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration;
// cutoff happens before or during this bucket; everything before
// this bucket should be removed.
if (curEnd > cutoff) break;
}
if (i > 0) {
final int length = bucketStart.length;
bucketStart = Arrays.copyOfRange(bucketStart, i, length);
rx = Arrays.copyOfRange(rx, i, length);
tx = Arrays.copyOfRange(tx, i, length);
bucketCount -= i;
}
}
public void dump(String prefix, PrintWriter pw) {
// TODO: consider stripping identity when dumping
pw.print(prefix);
pw.print("NetworkStatsHistory: networkType="); pw.print(networkType);
pw.print(" identity="); pw.print(identity);
pw.print(" uid="); pw.println(uid);
for (int i = 0; i < bucketCount; i++) {
pw.print(prefix);
pw.print(" timestamp="); pw.print(bucketStart[i]);
pw.print(" rx="); pw.print(rx[i]);
pw.print(" tx="); pw.println(tx[i]);
}
}
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
dump("", new PrintWriter(writer));
return writer.toString();
}
public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
public NetworkStatsHistory createFromParcel(Parcel in) {
return new NetworkStatsHistory(in);
}
public NetworkStatsHistory[] newArray(int size) {
return new NetworkStatsHistory[size];
}
};
private static long[] readLongArray(DataInputStream in) throws IOException {
final int size = in.readInt();
final long[] values = new long[size];
for (int i = 0; i < values.length; i++) {
values[i] = in.readLong();
}
return values;
}
private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
if (size > values.length) {
throw new IllegalArgumentException("size larger than length");
}
out.writeInt(size);
for (int i = 0; i < size; i++) {
out.writeLong(values[i]);
}
}
private static long[] readLongArray(Parcel in) {
final int size = in.readInt();
final long[] values = new long[size];
for (int i = 0; i < values.length; i++) {
values[i] = in.readLong();
}
return values;
}
private static void writeLongArray(Parcel out, long[] values, int size) {
if (size > values.length) {
throw new IllegalArgumentException("size larger than length");
}
out.writeInt(size);
for (int i = 0; i < size; i++) {
out.writeLong(values[i]);
}
}
}

View File

@@ -141,7 +141,8 @@ public class TrafficStats {
// subtract starting values and return delta
final NetworkStats profilingStop = getNetworkStatsForUid(context);
final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
final NetworkStats profilingDelta = profilingStop.subtract(
sActiveProfilingStart, false);
sActiveProfilingStart = null;
return profilingDelta;
}

View File

@@ -0,0 +1,210 @@
/*
* 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.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkStatsHistory.UID_ALL;
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.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
import junit.framework.TestCase;
import java.util.Random;
@SmallTest
public class NetworkStatsHistoryTest extends TestCase {
private static final String TAG = "NetworkStatsHistoryTest";
private static final long TEST_START = 1194220800000L;
private NetworkStatsHistory stats;
@Override
protected void tearDown() throws Exception {
super.tearDown();
if (stats != null) {
assertConsistent(stats);
}
}
public void testRecordSingleBucket() throws Exception {
final long BUCKET_SIZE = HOUR_IN_MILLIS;
stats = buildStats(BUCKET_SIZE);
// record data into narrow window to get single bucket
stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
assertEquals(1, stats.bucketCount);
assertBucket(stats, 0, 1024L, 2048L);
}
public void testRecordEqualBuckets() throws Exception {
final long bucketDuration = HOUR_IN_MILLIS;
stats = buildStats(bucketDuration);
// split equally across two buckets
final long recordStart = TEST_START + (bucketDuration / 2);
stats.recordData(recordStart, recordStart + bucketDuration, 1024L, 128L);
assertEquals(2, stats.bucketCount);
assertBucket(stats, 0, 512L, 64L);
assertBucket(stats, 1, 512L, 64L);
}
public void testRecordTouchingBuckets() throws Exception {
final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
stats = buildStats(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, 1000L, 5000L);
assertEquals(3, stats.bucketCount);
// first bucket should have (1/20 of value)
assertBucket(stats, 0, 50L, 250L);
// second bucket should have (15/20 of value)
assertBucket(stats, 1, 750L, 3750L);
// final bucket should have (4/20 of value)
assertBucket(stats, 2, 200L, 1000L);
}
public void testRecordGapBuckets() throws Exception {
final long BUCKET_SIZE = HOUR_IN_MILLIS;
stats = buildStats(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, 128L, 256L);
stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS, 64L, 512L);
// we should have two buckets, far apart from each other
assertEquals(2, stats.bucketCount);
assertBucket(stats, 0, 128L, 256L);
assertBucket(stats, 1, 64L, 512L);
// 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, 2048L, 2048L);
// now should have four buckets, with new record in middle two buckets
assertEquals(4, stats.bucketCount);
assertBucket(stats, 0, 128L, 256L);
assertBucket(stats, 1, 1024L, 1024L);
assertBucket(stats, 2, 1024L, 1024L);
assertBucket(stats, 3, 64L, 512L);
}
public void testRecordOverlapBuckets() throws Exception {
final long BUCKET_SIZE = HOUR_IN_MILLIS;
stats = buildStats(BUCKET_SIZE);
// record some data in one bucket, and another overlapping buckets
stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 256L, 256L);
final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
stats.recordData(midStart, midStart + HOUR_IN_MILLIS, 1024L, 1024L);
// should have two buckets, with some data mixed together
assertEquals(2, stats.bucketCount);
assertBucket(stats, 0, 768L, 768L);
assertBucket(stats, 1, 512L, 512L);
}
public void testRemove() throws Exception {
final long BUCKET_SIZE = HOUR_IN_MILLIS;
stats = buildStats(BUCKET_SIZE);
// record some data across 24 buckets
stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
assertEquals(24, stats.bucketCount);
// try removing far before buckets; should be no change
stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
assertEquals(24, stats.bucketCount);
// 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.bucketCount);
// try removing single bucket
stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
assertEquals(23, stats.bucketCount);
// try removing multiple buckets
stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
assertEquals(20, stats.bucketCount);
// try removing all buckets
stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
assertEquals(0, stats.bucketCount);
}
@Suppress
public void testFuzzing() throws Exception {
try {
// fuzzing with random events, looking for crashes
final Random r = new Random();
for (int i = 0; i < 500; i++) {
stats = buildStats(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();
stats.recordData(start, end, r.nextLong(), r.nextLong());
} else {
// trim something
stats.removeBucketsBefore(r.nextLong());
}
}
assertConsistent(stats);
}
} catch (Throwable e) {
Log.e(TAG, String.valueOf(stats));
throw new RuntimeException(e);
}
}
private static NetworkStatsHistory buildStats(long bucketSize) {
return new NetworkStatsHistory(TYPE_MOBILE, null, UID_ALL, bucketSize);
}
private static void assertConsistent(NetworkStatsHistory stats) {
// verify timestamps are monotonic
for (int i = 1; i < stats.bucketCount; i++) {
assertTrue(stats.bucketStart[i - 1] < stats.bucketStart[i]);
}
}
private static void assertBucket(NetworkStatsHistory stats, int index, long rx, long tx) {
assertEquals("unexpected rx", rx, stats.rx[index]);
assertEquals("unexpected tx", tx, stats.tx[index]);
}
}

View File

@@ -47,8 +47,9 @@ public class NetworkStatsTest extends TestCase {
.addEntry(TEST_IFACE, 100, 1024, 0)
.addEntry(TEST_IFACE, 101, 0, 1024).build();
final NetworkStats result = after.subtract(before);
final NetworkStats result = after.subtract(before, true);
// identical data should result in zero delta
assertEquals(0, result.rx[0]);
assertEquals(0, result.tx[0]);
assertEquals(0, result.rx[1]);
@@ -64,7 +65,7 @@ public class NetworkStatsTest extends TestCase {
.addEntry(TEST_IFACE, 100, 1025, 2)
.addEntry(TEST_IFACE, 101, 3, 1028).build();
final NetworkStats result = after.subtract(before);
final NetworkStats result = after.subtract(before, true);
// expect delta between measurements
assertEquals(1, result.rx[0]);
@@ -83,7 +84,7 @@ public class NetworkStatsTest extends TestCase {
.addEntry(TEST_IFACE, 101, 0, 1024)
.addEntry(TEST_IFACE, 102, 1024, 1024).build();
final NetworkStats result = after.subtract(before);
final NetworkStats result = after.subtract(before, true);
// its okay to have new rows
assertEquals(0, result.rx[0]);

View File

@@ -1,96 +0,0 @@
/*
* Copyright (C) 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
*
* 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 android.content.Context;
import android.net.TrafficStats;
import android.os.INetStatService;
import android.os.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
public class NetStatService extends INetStatService.Stub {
private final Context mContext;
public NetStatService(Context context) {
mContext = context;
}
public long getMobileTxPackets() {
return TrafficStats.getMobileTxPackets();
}
public long getMobileRxPackets() {
return TrafficStats.getMobileRxPackets();
}
public long getMobileTxBytes() {
return TrafficStats.getMobileTxBytes();
}
public long getMobileRxBytes() {
return TrafficStats.getMobileRxBytes();
}
public long getTotalTxPackets() {
return TrafficStats.getTotalTxPackets();
}
public long getTotalRxPackets() {
return TrafficStats.getTotalRxPackets();
}
public long getTotalTxBytes() {
return TrafficStats.getTotalTxBytes();
}
public long getTotalRxBytes() {
return TrafficStats.getTotalRxBytes();
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// This data is accessible to any app -- no permission check needed.
pw.print("Elapsed: total=");
pw.print(SystemClock.elapsedRealtime());
pw.print("ms awake=");
pw.print(SystemClock.uptimeMillis());
pw.println("ms");
pw.print("Mobile: Tx=");
pw.print(getMobileTxBytes());
pw.print("B/");
pw.print(getMobileTxPackets());
pw.print("Pkts Rx=");
pw.print(getMobileRxBytes());
pw.print("B/");
pw.print(getMobileRxPackets());
pw.println("Pkts");
pw.print("Total: Tx=");
pw.print(getTotalTxBytes());
pw.print("B/");
pw.print(getTotalTxPackets());
pw.print("Pkts Rx=");
pw.print(getTotalRxBytes());
pw.print("B/");
pw.print(getTotalRxPackets());
pw.println("Pkts");
}
}

View File

@@ -49,6 +49,7 @@ import com.android.internal.os.SamplingProfilerIntegration;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.am.ActivityManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.pm.PackageManagerService;
import com.android.server.usb.UsbService;
import com.android.server.wm.WindowManagerService;
@@ -116,7 +117,9 @@ class ServerThread extends Thread {
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
AlarmManagerService alarm = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
IPackageManager pm = null;
@@ -188,7 +191,7 @@ class ServerThread extends Thread {
power.init(context, lights, ActivityManagerService.getDefault(), battery);
Slog.i(TAG, "Alarm Manager");
AlarmManagerService alarm = new AlarmManagerService(context);
alarm = new AlarmManagerService(context);
ServiceManager.addService(Context.ALARM_SERVICE, alarm);
Slog.i(TAG, "Init Watchdog");
@@ -273,22 +276,6 @@ class ServerThread extends Thread {
Slog.e(TAG, "Failure starting Input Manager Service", e);
}
try {
Slog.i(TAG, "NetStat Service");
ServiceManager.addService("netstat", new NetStatService(context));
} catch (Throwable e) {
Slog.e(TAG, "Failure starting NetStat Service", e);
}
try {
Slog.i(TAG, "NetworkPolicy Service");
networkPolicy = new NetworkPolicyManagerService(
context, ActivityManagerService.self(), power);
ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Connectivity Service", e);
}
try {
Slog.i(TAG, "NetworkManagement Service");
networkManagement = NetworkManagementService.create(context);
@@ -297,6 +284,23 @@ class ServerThread extends Thread {
Slog.e(TAG, "Failure starting NetworkManagement Service", e);
}
try {
Slog.i(TAG, "NetworkStats Service");
networkStats = new NetworkStatsService(context, networkManagement, alarm);
ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting NetworkStats Service", e);
}
try {
Slog.i(TAG, "NetworkPolicy Service");
networkPolicy = new NetworkPolicyManagerService(
context, ActivityManagerService.self(), power, networkStats);
ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting NetworkPolicy Service", e);
}
try {
Slog.i(TAG, "Connectivity Service");
connectivity = new ConnectivityService(context, networkManagement, networkPolicy);
@@ -535,6 +539,7 @@ class ServerThread extends Thread {
// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
final NetworkStatsService networkStatsF = networkStats;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
@@ -561,6 +566,7 @@ class ServerThread extends Thread {
startSystemUi(contextF);
if (batteryF != null) batteryF.systemReady();
if (networkStatsF != null) networkStatsF.systemReady();
if (networkPolicyF != null) networkPolicyF.systemReady();
if (connectivityF != null) connectivityF.systemReady();
if (dockF != null) dockF.systemReady();

View File

@@ -35,10 +35,10 @@ import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.os.IPowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -59,11 +59,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String TAG = "NetworkPolicy";
private static final boolean LOGD = true;
private Context mContext;
private IActivityManager mActivityManager;
private IPowerManager mPowerManager;
private final Context mContext;
private final IActivityManager mActivityManager;
private final IPowerManager mPowerManager;
private final INetworkStatsService mNetworkStats;
private Object mRulesLock = new Object();
private final Object mRulesLock = new Object();
private boolean mScreenOn;
@@ -80,21 +81,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
INetworkPolicyListener>();
// TODO: periodically poll network stats and write to disk
// TODO: save/restore policy information from disk
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
public NetworkPolicyManagerService(
Context context, IActivityManager activityManager, IPowerManager powerManager) {
// TODO: keep record of billing cycle details, and limit rules
// TODO: keep map of interfaces-to-billing-relationship
public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
IPowerManager powerManager, INetworkStatsService networkStats) {
mContext = checkNotNull(context, "missing context");
mActivityManager = checkNotNull(activityManager, "missing activityManager");
mPowerManager = checkNotNull(powerManager, "missing powerManager");
mNetworkStats = checkNotNull(networkStats, "missing networkStats");
}
public void systemReady() {
// TODO: read current policy+stats from disk and generate NMS rules
// TODO: read current policy from disk
updateScreenOn();
@@ -114,18 +118,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, screenFilter);
final IntentFilter shutdownFilter = new IntentFilter();
shutdownFilter.addAction(Intent.ACTION_SHUTDOWN);
mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
}
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(
MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// because a uid can have multiple pids running inside, we need to
@@ -145,8 +144,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
public void onProcessDied(int pid, int uid) {
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(
MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// clear records and recompute, when they exist
@@ -170,19 +168,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
};
private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO: persist any pending stats during clean shutdown
Log.d(TAG, "persisting stats");
}
};
@Override
public void setUidPolicy(int uid, int policy) {
// TODO: create permission for modifying data policy
mContext.enforceCallingOrSelfPermission(
UPDATE_DEVICE_STATS, "requires UPDATE_DEVICE_STATS permission");
mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
final int oldPolicy;
synchronized (mRulesLock) {
@@ -228,7 +217,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, "requires DUMP permission");
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
synchronized (mRulesLock) {
fout.println("Policy status for known UIDs:");
@@ -366,7 +355,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
return value;
}
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {

View File

@@ -0,0 +1,403 @@
/*
* Copyright (C) 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
*
* 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.net;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.SHUTDOWN;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkStats.UID_ALL;
import android.app.AlarmManager;
import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.util.NtpTrustedTime;
import android.util.Slog;
import android.util.TrustedTime;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyIntents;
import com.google.android.collect.Maps;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
/**
* Collect and persist detailed network statistics, and provide this data to
* other system services.
*/
public class NetworkStatsService extends INetworkStatsService.Stub {
private static final String TAG = "NetworkStatsService";
private static final boolean LOGD = true;
private final Context mContext;
private final INetworkManagementService mNetworkManager;
private final IAlarmManager mAlarmManager;
private final TrustedTime mTime;
private static final String ACTION_NETWORK_STATS_POLL =
"com.android.server.action.NETWORK_STATS_POLL";
private PendingIntent mPollIntent;
// TODO: move tweakable params to Settings.Secure
// TODO: listen for kernel push events through netd instead of polling
private static final long KB_IN_BYTES = 1024;
private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
private static final long SUMMARY_BUCKET_DURATION = 6 * DateUtils.HOUR_IN_MILLIS;
private static final long SUMMARY_MAX_HISTORY = 90 * DateUtils.DAY_IN_MILLIS;
// TODO: remove these high-frequency testing values
// private static final long POLL_INTERVAL = 5 * DateUtils.SECOND_IN_MILLIS;
// private static final long SUMMARY_BUCKET_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
// private static final long SUMMARY_MAX_HISTORY = 2 * DateUtils.MINUTE_IN_MILLIS;
/** Minimum delta required to persist to disk. */
private static final long SUMMARY_PERSIST_THRESHOLD = 64 * KB_IN_BYTES;
private static final long TIME_CACHE_MAX_AGE = DateUtils.DAY_IN_MILLIS;
private final Object mStatsLock = new Object();
/** Set of active ifaces during this boot. */
private HashMap<String, InterfaceInfo> mActiveIface = Maps.newHashMap();
/** Set of historical stats for known ifaces. */
private HashMap<InterfaceInfo, NetworkStatsHistory> mIfaceStats = Maps.newHashMap();
private NetworkStats mLastPollStats;
private NetworkStats mLastPersistStats;
private final HandlerThread mHandlerThread;
private final Handler mHandler;
// TODO: collect detailed uid stats, storing tag-granularity data until next
// dropbox, and uid summary for a specific bucket count.
// TODO: periodically compile statistics and send to dropbox.
public NetworkStatsService(
Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
// TODO: move to using cached NtpTrustedTime
this(context, networkManager, alarmManager, new NtpTrustedTime());
}
public NetworkStatsService(Context context, INetworkManagementService networkManager,
IAlarmManager alarmManager, TrustedTime time) {
mContext = checkNotNull(context, "missing Context");
mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
mTime = checkNotNull(time, "missing TrustedTime");
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
}
public void systemReady() {
// read historical stats from disk
readStatsLocked();
// watch other system services that claim interfaces
// TODO: protect incoming broadcast with permissions check.
// TODO: consider migrating this to ConnectivityService, but it might
// cause a circular dependency.
final IntentFilter interfaceFilter = new IntentFilter();
interfaceFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
interfaceFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mContext.registerReceiver(mInterfaceReceiver, interfaceFilter);
// listen for periodic polling events
final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
mContext.registerReceiver(mPollReceiver, pollFilter, UPDATE_DEVICE_STATS, mHandler);
// persist stats during clean shutdown
final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null);
try {
registerPollAlarmLocked();
} catch (RemoteException e) {
Slog.w(TAG, "unable to register poll alarm");
}
}
/**
* Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
* reschedule based on current {@link #POLL_INTERVAL} value.
*/
private void registerPollAlarmLocked() throws RemoteException {
if (mPollIntent != null) {
mAlarmManager.remove(mPollIntent);
}
mPollIntent = PendingIntent.getBroadcast(
mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
final long currentRealtime = SystemClock.elapsedRealtime();
mAlarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME, currentRealtime, POLL_INTERVAL, mPollIntent);
}
@Override
public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) {
// TODO: return history for requested types
return null;
}
@Override
public NetworkStatsHistory getNetworkStatsUid(int uid) {
// TODO: return history for requested uid
return null;
}
/**
* Receiver that watches for other system components that claim network
* interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
* with mobile interfaces.
*/
private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED.equals(action)) {
final LinkProperties prop = intent.getParcelableExtra(
Phone.DATA_LINK_PROPERTIES_KEY);
final String iface = prop != null ? prop.getInterfaceName() : null;
if (iface != null) {
final TelephonyManager teleManager = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
final InterfaceInfo info = new InterfaceInfo(
iface, TYPE_MOBILE, teleManager.getSubscriberId());
reportActiveInterface(info);
}
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
final LinkProperties prop = intent.getParcelableExtra(
WifiManager.EXTRA_LINK_PROPERTIES);
final String iface = prop != null ? prop.getInterfaceName() : null;
if (iface != null) {
final InterfaceInfo info = new InterfaceInfo(iface, TYPE_WIFI, null);
reportActiveInterface(info);
}
}
}
};
private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// already running on background handler, network/io is safe, and
// caller verified to have UPDATE_DEVICE_STATS permission above.
synchronized (mStatsLock) {
// TODO: acquire wakelock while performing poll
performPollLocked();
}
}
};
private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// persist stats during clean shutdown
synchronized (mStatsLock) {
writeStatsLocked();
}
}
};
private void performPollLocked() {
if (LOGD) Slog.v(TAG, "performPollLocked()");
// try refreshing time source when stale
if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
mTime.forceRefresh();
}
// TODO: consider marking "untrusted" times in historical stats
final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
: System.currentTimeMillis();
final NetworkStats current;
try {
current = mNetworkManager.getNetworkStatsSummary();
} catch (RemoteException e) {
Slog.w(TAG, "problem reading network stats");
return;
}
// update historical usage with delta since last poll
final NetworkStats pollDelta = computeStatsDelta(mLastPollStats, current);
final long timeStart = currentTime - pollDelta.elapsedRealtime;
for (String iface : pollDelta.getKnownIfaces()) {
final InterfaceInfo info = mActiveIface.get(iface);
if (info == null) {
if (LOGD) Slog.w(TAG, "unknown interface " + iface + ", ignoring stats");
continue;
}
final int index = pollDelta.findIndex(iface, UID_ALL);
final long rx = pollDelta.rx[index];
final long tx = pollDelta.tx[index];
final NetworkStatsHistory history = findOrCreateHistoryLocked(info);
history.recordData(timeStart, currentTime, rx, tx);
history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY);
}
mLastPollStats = current;
// decide if enough has changed to trigger persist
final NetworkStats persistDelta = computeStatsDelta(mLastPersistStats, current);
for (String iface : persistDelta.getKnownIfaces()) {
final int index = persistDelta.findIndex(iface, UID_ALL);
if (persistDelta.rx[index] > SUMMARY_PERSIST_THRESHOLD
|| persistDelta.tx[index] > SUMMARY_PERSIST_THRESHOLD) {
writeStatsLocked();
mLastPersistStats = current;
break;
}
}
}
private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) {
NetworkStatsHistory stats = mIfaceStats.get(info);
if (stats == null) {
stats = new NetworkStatsHistory(
info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION);
mIfaceStats.put(info, stats);
}
return stats;
}
private void readStatsLocked() {
if (LOGD) Slog.v(TAG, "readStatsLocked()");
// TODO: read historical stats from disk using AtomicFile
}
private void writeStatsLocked() {
if (LOGD) Slog.v(TAG, "writeStatsLocked()");
// TODO: persist historical stats to disk using AtomicFile
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
pw.println("Active interfaces:");
for (InterfaceInfo info : mActiveIface.values()) {
info.dump(" ", pw);
}
pw.println("Known historical stats:");
for (NetworkStatsHistory stats : mIfaceStats.values()) {
stats.dump(" ", pw);
}
}
/**
* Details for a well-known network interface, including its name, network
* type, and billing relationship identity (such as IMSI).
*/
private static class InterfaceInfo {
public final String iface;
public final int networkType;
public final String identity;
public InterfaceInfo(String iface, int networkType, String identity) {
this.iface = iface;
this.networkType = networkType;
this.identity = identity;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((identity == null) ? 0 : identity.hashCode());
result = prime * result + ((iface == null) ? 0 : iface.hashCode());
result = prime * result + networkType;
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof InterfaceInfo) {
final InterfaceInfo info = (InterfaceInfo) obj;
return equal(iface, info.iface) && networkType == info.networkType
&& equal(identity, info.identity);
}
return false;
}
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("InterfaceInfo: iface="); pw.print(iface);
pw.print(" networkType="); pw.print(networkType);
pw.print(" identity="); pw.println(identity);
}
}
private void reportActiveInterface(InterfaceInfo info) {
synchronized (mStatsLock) {
// TODO: when interface redefined, port over historical stats
mActiveIface.put(info.iface, info);
}
}
/**
* Return the delta between two {@link NetworkStats} snapshots, where {@code
* before} can be {@code null}.
*/
private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) {
if (before != null) {
return current.subtract(before, false);
} else {
return current;
}
}
private static boolean equal(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
private static <T> T checkNotNull(T value, String message) {
if (value == null) {
throw new NullPointerException(message);
}
return value;
}
}

View File

@@ -32,6 +32,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.INetworkPolicyListener;
import android.net.INetworkStatsService;
import android.os.Binder;
import android.os.IPowerManager;
import android.test.AndroidTestCase;
@@ -57,6 +58,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
private IActivityManager mActivityManager;
private IPowerManager mPowerManager;
private INetworkStatsService mStatsService;
private INetworkPolicyListener mPolicyListener;
private NetworkPolicyManagerService mService;
@@ -90,10 +92,11 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
mActivityManager = createMock(IActivityManager.class);
mPowerManager = createMock(IPowerManager.class);
mStatsService = createMock(INetworkStatsService.class);
mPolicyListener = createMock(INetworkPolicyListener.class);
mService = new NetworkPolicyManagerService(
mServiceContext, mActivityManager, mPowerManager);
mServiceContext, mActivityManager, mPowerManager, mStatsService);
// RemoteCallbackList needs a binder to use as key
expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce();
@@ -123,6 +126,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
mActivityManager = null;
mPowerManager = null;
mStatsService = null;
mPolicyListener = null;
mService = null;
@@ -262,11 +266,11 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
}
private void replay() {
EasyMock.replay(mActivityManager, mPowerManager, mPolicyListener);
EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
}
private void verifyAndReset() {
EasyMock.verify(mActivityManager, mPowerManager, mPolicyListener);
EasyMock.reset(mActivityManager, mPowerManager, mPolicyListener);
EasyMock.verify(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
EasyMock.reset(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
}
}