am 45470559: (-s ours) am 12b7da69: am 6df477be: Merge "Rename (IF_)LOGE(_IF) to (IF_)ALOGE(_IF)"
* commit '45470559cd4a499fd5d38cf708f0b531dc081fed':
Rename (IF_)LOGE(_IF) to (IF_)ALOGE(_IF)
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index aa6400b..1a74abf 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -45,7 +45,7 @@
@Override
public int hashCode() {
- return Objects.hashCode(mType, mSubType, mSubscriberId);
+ return Objects.hashCode(mType, mSubType, mSubscriberId, mRoaming);
}
@Override
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index f6e627c..e8f60b4 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -16,8 +16,6 @@
package android.net;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -40,8 +38,6 @@
* @hide
*/
public class NetworkStats implements Parcelable {
- private static final String TAG = "NetworkStats";
-
/** {@link #iface} value when interface details unavailable. */
public static final String IFACE_ALL = null;
/** {@link #uid} value when UID details unavailable. */
@@ -463,62 +459,64 @@
* between two snapshots in time. Assumes that statistics rows collect over
* time, and that none of them have disappeared.
*/
- public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
- return subtract(value, false);
+ public NetworkStats subtract(NetworkStats right) {
+ return subtract(this, right, null);
}
/**
- * Subtract the given {@link NetworkStats}, effectively leaving the delta
+ * Subtract the two given {@link NetworkStats} objects, returning the delta
* between two snapshots in time. Assumes that statistics rows collect over
* time, and that none of them have disappeared.
- *
- * @param clampNonMonotonic When non-monotonic stats are found, just clamp
- * to 0 instead of throwing {@link NonMonotonicException}.
+ * <p>
+ * If counters have rolled backwards, they are clamped to {@code 0} and
+ * reported to the given {@link NonMonotonicObserver}.
*/
- public NetworkStats subtract(NetworkStats value, boolean clampNonMonotonic)
- throws NonMonotonicException {
- final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
+ public static NetworkStats subtract(
+ NetworkStats left, NetworkStats right, NonMonotonicObserver observer) {
+ long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
if (deltaRealtime < 0) {
- throw new NonMonotonicException(this, value);
+ if (observer != null) {
+ observer.foundNonMonotonic(left, -1, right, -1);
+ }
+ deltaRealtime = 0;
}
// result will have our rows, and elapsed time between snapshots
final Entry entry = new Entry();
- final NetworkStats result = new NetworkStats(deltaRealtime, size);
- for (int i = 0; i < size; i++) {
- entry.iface = iface[i];
- entry.uid = uid[i];
- entry.set = set[i];
- entry.tag = tag[i];
+ final NetworkStats result = new NetworkStats(deltaRealtime, left.size);
+ for (int i = 0; i < left.size; i++) {
+ entry.iface = left.iface[i];
+ entry.uid = left.uid[i];
+ entry.set = left.set[i];
+ entry.tag = left.tag[i];
// find remote row that matches, and subtract
- final int j = value.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
+ final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
if (j == -1) {
// newly appearing row, return entire value
- entry.rxBytes = rxBytes[i];
- entry.rxPackets = rxPackets[i];
- entry.txBytes = txBytes[i];
- entry.txPackets = txPackets[i];
- entry.operations = operations[i];
+ entry.rxBytes = left.rxBytes[i];
+ entry.rxPackets = left.rxPackets[i];
+ entry.txBytes = left.txBytes[i];
+ entry.txPackets = left.txPackets[i];
+ entry.operations = left.operations[i];
} else {
// existing row, subtract remote value
- entry.rxBytes = rxBytes[i] - value.rxBytes[j];
- entry.rxPackets = rxPackets[i] - value.rxPackets[j];
- entry.txBytes = txBytes[i] - value.txBytes[j];
- entry.txPackets = txPackets[i] - value.txPackets[j];
- entry.operations = operations[i] - value.operations[j];
+ entry.rxBytes = left.rxBytes[i] - right.rxBytes[j];
+ entry.rxPackets = left.rxPackets[i] - right.rxPackets[j];
+ entry.txBytes = left.txBytes[i] - right.txBytes[j];
+ entry.txPackets = left.txPackets[i] - right.txPackets[j];
+ entry.operations = left.operations[i] - right.operations[j];
if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
|| entry.txPackets < 0 || entry.operations < 0) {
- if (clampNonMonotonic) {
- entry.rxBytes = Math.max(entry.rxBytes, 0);
- entry.rxPackets = Math.max(entry.rxPackets, 0);
- entry.txBytes = Math.max(entry.txBytes, 0);
- entry.txPackets = Math.max(entry.txPackets, 0);
- entry.operations = Math.max(entry.operations, 0);
- } else {
- throw new NonMonotonicException(this, i, value, j);
+ if (observer != null) {
+ observer.foundNonMonotonic(left, i, right, j);
}
+ entry.rxBytes = Math.max(entry.rxBytes, 0);
+ entry.rxPackets = Math.max(entry.rxPackets, 0);
+ entry.txBytes = Math.max(entry.txBytes, 0);
+ entry.txPackets = Math.max(entry.txPackets, 0);
+ entry.operations = Math.max(entry.operations, 0);
}
}
@@ -665,22 +663,8 @@
}
};
- public static class NonMonotonicException extends Exception {
- public final NetworkStats left;
- public final NetworkStats right;
- public final int leftIndex;
- public final int rightIndex;
-
- public NonMonotonicException(NetworkStats left, NetworkStats right) {
- this(left, -1, right, -1);
- }
-
- public NonMonotonicException(
- NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
- this.left = checkNotNull(left, "missing left");
- this.right = checkNotNull(right, "missing right");
- this.leftIndex = leftIndex;
- this.rightIndex = rightIndex;
- }
+ public interface NonMonotonicObserver {
+ public void foundNonMonotonic(
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex);
}
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index cd585b2..8bdb669 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -20,7 +20,6 @@
import android.app.backup.BackupManager;
import android.content.Context;
import android.media.MediaPlayer;
-import android.net.NetworkStats.NonMonotonicException;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -193,15 +192,12 @@
throw new IllegalStateException("not profiling data");
}
- try {
- // subtract starting values and return delta
- final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
- final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
- sActiveProfilingStart = null;
- return profilingDelta;
- } catch (NonMonotonicException e) {
- throw new RuntimeException(e);
- }
+ // subtract starting values and return delta
+ final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
+ final NetworkStats profilingDelta = NetworkStats.subtract(
+ profilingStop, sActiveProfilingStart, null);
+ sActiveProfilingStart = null;
+ return profilingDelta;
}
}
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 28013bd..f475dd6 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -22,59 +22,51 @@
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
+import android.util.LocalLog;
import android.util.Slog;
+import com.google.android.collect.Lists;
+
+import java.nio.charset.Charsets;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
- * Generic connector class for interfacing with a native
- * daemon which uses the libsysutils FrameworkListener
- * protocol.
+ * Generic connector class for interfacing with a native daemon which uses the
+ * {@code libsysutils} FrameworkListener protocol.
*/
final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
- private static final boolean LOCAL_LOGD = false;
+ private static final boolean LOGD = false;
- private BlockingQueue<String> mResponseQueue;
- private OutputStream mOutputStream;
- private String TAG = "NativeDaemonConnector";
- private String mSocket;
+ private final String TAG;
+
+ private String mSocket;
+ private OutputStream mOutputStream;
+ private LocalLog mLocalLog;
+
+ private final BlockingQueue<NativeDaemonEvent> mResponseQueue;
+
private INativeDaemonConnectorCallbacks mCallbacks;
- private Handler mCallbackHandler;
+ private Handler mCallbackHandler;
/** Lock held whenever communicating with native daemon. */
- private Object mDaemonLock = new Object();
+ private final Object mDaemonLock = new Object();
private final int BUFFER_SIZE = 4096;
- class ResponseCode {
- public static final int ActionInitiated = 100;
-
- public static final int CommandOkay = 200;
-
- // The range of 400 -> 599 is reserved for cmd failures
- public static final int OperationFailed = 400;
- public static final int CommandSyntaxError = 500;
- public static final int CommandParameterError = 501;
-
- public static final int UnsolicitedInformational = 600;
-
- //
- public static final int FailedRangeStart = 400;
- public static final int FailedRangeEnd = 599;
- }
-
- NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
- String socket, int responseQueueSize, String logTag) {
+ NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
+ int responseQueueSize, String logTag, int maxLogSize) {
mCallbacks = callbacks;
- if (logTag != null)
- TAG = logTag;
mSocket = socket;
- mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
+ mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize);
+ TAG = logTag != null ? logTag : "NativeDaemonConnector";
+ mLocalLog = new LocalLog(maxLogSize);
}
@Override
@@ -136,26 +128,28 @@
for (int i = 0; i < count; i++) {
if (buffer[i] == 0) {
- String event = new String(buffer, start, i - start);
- if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
+ final String rawEvent = new String(
+ buffer, start, i - start, Charsets.UTF_8);
+ log("RCV <- {" + rawEvent + "}");
- String[] tokens = event.split(" ", 2);
try {
- int code = Integer.parseInt(tokens[0]);
-
- if (code >= ResponseCode.UnsolicitedInformational) {
- mCallbackHandler.sendMessage(
- mCallbackHandler.obtainMessage(code, event));
+ final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
+ rawEvent);
+ if (event.isClassUnsolicited()) {
+ // TODO: migrate to sending NativeDaemonEvent instances
+ mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
+ event.getCode(), event.getRawEvent()));
} else {
try {
mResponseQueue.put(event);
} catch (InterruptedException ex) {
- Slog.e(TAG, "Failed to put response onto queue", ex);
+ Slog.e(TAG, "Failed to put response onto queue: " + ex);
}
}
- } catch (NumberFormatException nfe) {
- Slog.w(TAG, String.format("Bad msg (%s)", event));
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Problem parsing message: " + rawEvent, e);
}
+
start = i + 1;
}
}
@@ -195,137 +189,261 @@
}
}
- private void sendCommandLocked(String command) throws NativeDaemonConnectorException {
- sendCommandLocked(command, null);
- }
-
/**
- * Sends a command to the daemon with a single argument
+ * Send command to daemon, escaping arguments as needed.
*
- * @param command The command to send to the daemon
- * @param argument The argument to send with the command (or null)
+ * @return the final command issued.
*/
- private void sendCommandLocked(String command, String argument)
+ private String sendCommandLocked(String cmd, Object... args)
throws NativeDaemonConnectorException {
- if (command != null && command.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("unexpected command: " + command);
- }
- if (argument != null && argument.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("unexpected argument: " + argument);
+ // TODO: eventually enforce that cmd doesn't contain arguments
+ if (cmd.indexOf('\0') >= 0) {
+ throw new IllegalArgumentException("unexpected command: " + cmd);
}
- if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
+ final StringBuilder builder = new StringBuilder(cmd);
+ for (Object arg : args) {
+ final String argString = String.valueOf(arg);
+ if (argString.indexOf('\0') >= 0) {
+ throw new IllegalArgumentException("unexpected argument: " + arg);
+ }
+
+ builder.append(' ');
+ appendEscaped(builder, argString);
+ }
+
+ final String unterminated = builder.toString();
+ log("SND -> {" + unterminated + "}");
+
+ builder.append('\0');
+
if (mOutputStream == null) {
- Slog.e(TAG, "No connection to daemon", new IllegalStateException());
- throw new NativeDaemonConnectorException("No output stream!");
+ throw new NativeDaemonConnectorException("missing output stream");
} else {
- StringBuilder builder = new StringBuilder(command);
- if (argument != null) {
- builder.append(argument);
- }
- builder.append('\0');
-
try {
- mOutputStream.write(builder.toString().getBytes());
- } catch (IOException ex) {
- Slog.e(TAG, "IOException in sendCommand", ex);
+ mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8));
+ } catch (IOException e) {
+ throw new NativeDaemonConnectorException("problem sending command", e);
}
}
+
+ return unterminated;
}
/**
- * Issue a command to the native daemon and return the responses
+ * Issue the given command to the native daemon and return a single expected
+ * response.
+ *
+ * @throws NativeDaemonConnectorException when problem communicating with
+ * native daemon, or if the response matches
+ * {@link NativeDaemonEvent#isClassClientError()} or
+ * {@link NativeDaemonEvent#isClassServerError()}.
*/
- public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
- synchronized (mDaemonLock) {
- return doCommandLocked(cmd);
- }
- }
-
- private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException {
- mResponseQueue.clear();
- sendCommandLocked(cmd);
-
- ArrayList<String> response = new ArrayList<String>();
- boolean complete = false;
- int code = -1;
-
- while (!complete) {
- try {
- // TODO - this should not block forever
- String line = mResponseQueue.take();
- if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
- String[] tokens = line.split(" ");
- try {
- code = Integer.parseInt(tokens[0]);
- } catch (NumberFormatException nfe) {
- throw new NativeDaemonConnectorException(
- String.format("Invalid response from daemon (%s)", line));
- }
-
- if ((code >= 200) && (code < 600)) {
- complete = true;
- }
- response.add(line);
- } catch (InterruptedException ex) {
- Slog.e(TAG, "Failed to process response", ex);
- }
- }
-
- if (code >= ResponseCode.FailedRangeStart &&
- code <= ResponseCode.FailedRangeEnd) {
- /*
- * Note: The format of the last response in this case is
- * "NNN <errmsg>"
- */
- throw new NativeDaemonConnectorException(
- code, cmd, response.get(response.size()-1).substring(4));
- }
- return response;
+ public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
+ return execute(cmd.mCmd, cmd.mArguments.toArray());
}
/**
- * Issues a list command and returns the cooked list
+ * Issue the given command to the native daemon and return a single expected
+ * response.
+ *
+ * @throws NativeDaemonConnectorException when problem communicating with
+ * native daemon, or if the response matches
+ * {@link NativeDaemonEvent#isClassClientError()} or
+ * {@link NativeDaemonEvent#isClassServerError()}.
*/
- public String[] doListCommand(String cmd, int expectedResponseCode)
+ public NativeDaemonEvent execute(String cmd, Object... args)
throws NativeDaemonConnectorException {
+ final NativeDaemonEvent[] events = executeForList(cmd, args);
+ if (events.length != 1) {
+ throw new NativeDaemonConnectorException(
+ "Expected exactly one response, but received " + events.length);
+ }
+ return events[0];
+ }
- ArrayList<String> rsp = doCommand(cmd);
- String[] rdata = new String[rsp.size()-1];
- int idx = 0;
+ /**
+ * Issue the given command to the native daemon and return any
+ * {@link NativeDaemonEvent#isClassContinue()} responses, including the
+ * final terminal response.
+ *
+ * @throws NativeDaemonConnectorException when problem communicating with
+ * native daemon, or if the response matches
+ * {@link NativeDaemonEvent#isClassClientError()} or
+ * {@link NativeDaemonEvent#isClassServerError()}.
+ */
+ public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
+ return executeForList(cmd.mCmd, cmd.mArguments.toArray());
+ }
- for (int i = 0; i < rsp.size(); i++) {
- String line = rsp.get(i);
+ /**
+ * Issue the given command to the native daemon and return any
+ * {@link NativeDaemonEvent#isClassContinue()} responses, including the
+ * final terminal response.
+ *
+ * @throws NativeDaemonConnectorException when problem communicating with
+ * native daemon, or if the response matches
+ * {@link NativeDaemonEvent#isClassClientError()} or
+ * {@link NativeDaemonEvent#isClassServerError()}.
+ */
+ public NativeDaemonEvent[] executeForList(String cmd, Object... args)
+ throws NativeDaemonConnectorException {
+ synchronized (mDaemonLock) {
+ return executeLocked(cmd, args);
+ }
+ }
+
+ private NativeDaemonEvent[] executeLocked(String cmd, Object... args)
+ throws NativeDaemonConnectorException {
+ final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
+
+ mResponseQueue.clear();
+
+ final String sentCommand = sendCommandLocked(cmd, args);
+
+ NativeDaemonEvent event = null;
+ do {
try {
- String[] tok = line.split(" ");
- int code = Integer.parseInt(tok[0]);
- if (code == expectedResponseCode) {
- rdata[idx++] = line.substring(tok[0].length() + 1);
- } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
- if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
- int last = rsp.size() -1;
- if (i != last) {
- Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
- for (int j = i; j <= last ; j++) {
- Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
- }
- }
- return rdata;
- } else {
- throw new NativeDaemonConnectorException(
- String.format("Expected list response %d, but got %d",
- expectedResponseCode, code));
- }
- } catch (NumberFormatException nfe) {
+ event = mResponseQueue.take();
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "interrupted waiting for event line");
+ continue;
+ }
+ events.add(event);
+ } while (event.isClassContinue());
+
+ if (event.isClassClientError()) {
+ throw new NativeDaemonArgumentException(sentCommand, event);
+ }
+ if (event.isClassServerError()) {
+ throw new NativeDaemonFailureException(sentCommand, event);
+ }
+
+ return events.toArray(new NativeDaemonEvent[events.size()]);
+ }
+
+ /**
+ * Issue a command to the native daemon and return the raw responses.
+ *
+ * @deprecated callers should move to {@link #execute(String, Object...)}
+ * which returns parsed {@link NativeDaemonEvent}.
+ */
+ @Deprecated
+ public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
+ final ArrayList<String> rawEvents = Lists.newArrayList();
+ final NativeDaemonEvent[] events = executeForList(cmd);
+ for (NativeDaemonEvent event : events) {
+ rawEvents.add(event.getRawEvent());
+ }
+ return rawEvents;
+ }
+
+ /**
+ * Issues a list command and returns the cooked list of all
+ * {@link NativeDaemonEvent#getMessage()} which match requested code.
+ */
+ @Deprecated
+ public String[] doListCommand(String cmd, int expectedCode)
+ throws NativeDaemonConnectorException {
+ final ArrayList<String> list = Lists.newArrayList();
+
+ final NativeDaemonEvent[] events = executeForList(cmd);
+ for (int i = 0; i < events.length - 1; i++) {
+ final NativeDaemonEvent event = events[i];
+ final int code = event.getCode();
+ if (code == expectedCode) {
+ list.add(event.getMessage());
+ } else {
throw new NativeDaemonConnectorException(
- String.format("Error reading code '%s'", line));
+ "unexpected list response " + code + " instead of " + expectedCode);
}
}
- throw new NativeDaemonConnectorException("Got an empty response");
+
+ final NativeDaemonEvent finalEvent = events[events.length - 1];
+ if (!finalEvent.isClassOk()) {
+ throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
+ }
+
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Append the given argument to {@link StringBuilder}, escaping as needed,
+ * and surrounding with quotes when it contains spaces.
+ */
+ // @VisibleForTesting
+ static void appendEscaped(StringBuilder builder, String arg) {
+ final boolean hasSpaces = arg.indexOf(' ') >= 0;
+ if (hasSpaces) {
+ builder.append('"');
+ }
+
+ final int length = arg.length();
+ for (int i = 0; i < length; i++) {
+ final char c = arg.charAt(i);
+
+ if (c == '"') {
+ builder.append("\\\"");
+ } else if (c == '\\') {
+ builder.append("\\\\");
+ } else {
+ builder.append(c);
+ }
+ }
+
+ if (hasSpaces) {
+ builder.append('"');
+ }
+ }
+
+ private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
+ public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
+ super(command, event);
+ }
+
+ @Override
+ public IllegalArgumentException rethrowAsParcelableException() {
+ throw new IllegalArgumentException(getMessage(), this);
+ }
+ }
+
+ private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
+ public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
+ super(command, event);
+ }
+ }
+
+ /**
+ * Command builder that handles argument list building.
+ */
+ public static class Command {
+ private String mCmd;
+ private ArrayList<Object> mArguments = Lists.newArrayList();
+
+ public Command(String cmd, Object... args) {
+ mCmd = cmd;
+ for (Object arg : args) {
+ appendArg(arg);
+ }
+ }
+
+ public Command appendArg(Object arg) {
+ mArguments.add(arg);
+ return this;
+ }
}
/** {@inheritDoc} */
public void monitor() {
synchronized (mDaemonLock) { }
}
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLocalLog.dump(fd, pw, args);
+ }
+
+ private void log(String logstring) {
+ if (LOGD) Slog.d(TAG, logstring);
+ mLocalLog.log(logstring);
+ }
}
diff --git a/services/java/com/android/server/NativeDaemonConnectorException.java b/services/java/com/android/server/NativeDaemonConnectorException.java
index 426742b..590bbcc 100644
--- a/services/java/com/android/server/NativeDaemonConnectorException.java
+++ b/services/java/com/android/server/NativeDaemonConnectorException.java
@@ -16,33 +16,43 @@
package com.android.server;
+import android.os.Parcel;
+
/**
- * An exception that indicates there was an error with a NativeDaemonConnector operation
+ * An exception that indicates there was an error with a
+ * {@link NativeDaemonConnector} operation.
*/
-public class NativeDaemonConnectorException extends RuntimeException
-{
- private int mCode = -1;
+public class NativeDaemonConnectorException extends Exception {
private String mCmd;
+ private NativeDaemonEvent mEvent;
- public NativeDaemonConnectorException() {}
-
- public NativeDaemonConnectorException(String error)
- {
- super(error);
+ public NativeDaemonConnectorException(String detailMessage) {
+ super(detailMessage);
}
- public NativeDaemonConnectorException(int code, String cmd, String error)
- {
- super(String.format("Cmd {%s} failed with code %d : {%s}", cmd, code, error));
- mCode = code;
+ public NativeDaemonConnectorException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) {
+ super("command '" + cmd + "' failed with '" + event + "'");
mCmd = cmd;
+ mEvent = event;
}
public int getCode() {
- return mCode;
+ return mEvent.getCode();
}
public String getCmd() {
return mCmd;
}
+
+ /**
+ * Rethrow as a {@link RuntimeException} subclass that is handled by
+ * {@link Parcel#writeException(Exception)}.
+ */
+ public IllegalArgumentException rethrowAsParcelableException() {
+ throw new IllegalStateException(getMessage(), this);
+ }
}
diff --git a/services/java/com/android/server/NativeDaemonEvent.java b/services/java/com/android/server/NativeDaemonEvent.java
new file mode 100644
index 0000000..62084c0
--- /dev/null
+++ b/services/java/com/android/server/NativeDaemonEvent.java
@@ -0,0 +1,142 @@
+/*
+ * 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 com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+
+/**
+ * Parsed event from native side of {@link NativeDaemonConnector}.
+ */
+public class NativeDaemonEvent {
+
+ // TODO: keep class ranges in sync with ResponseCode.h
+ // TODO: swap client and server error ranges to roughly mirror HTTP spec
+
+ private final int mCode;
+ private final String mMessage;
+ private final String mRawEvent;
+
+ private NativeDaemonEvent(int code, String message, String rawEvent) {
+ mCode = code;
+ mMessage = message;
+ mRawEvent = rawEvent;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ @Deprecated
+ public String getRawEvent() {
+ return mRawEvent;
+ }
+
+ @Override
+ public String toString() {
+ return mRawEvent;
+ }
+
+ /**
+ * Test if event represents a partial response which is continued in
+ * additional subsequent events.
+ */
+ public boolean isClassContinue() {
+ return mCode >= 100 && mCode < 200;
+ }
+
+ /**
+ * Test if event represents a command success.
+ */
+ public boolean isClassOk() {
+ return mCode >= 200 && mCode < 300;
+ }
+
+ /**
+ * Test if event represents a remote native daemon error.
+ */
+ public boolean isClassServerError() {
+ return mCode >= 400 && mCode < 500;
+ }
+
+ /**
+ * Test if event represents a command syntax or argument error.
+ */
+ public boolean isClassClientError() {
+ return mCode >= 500 && mCode < 600;
+ }
+
+ /**
+ * Test if event represents an unsolicited event from native daemon.
+ */
+ public boolean isClassUnsolicited() {
+ return mCode >= 600 && mCode < 700;
+ }
+
+ /**
+ * Verify this event matches the given code.
+ *
+ * @throws IllegalStateException if {@link #getCode()} doesn't match.
+ */
+ public void checkCode(int code) {
+ if (mCode != code) {
+ throw new IllegalStateException("Expected " + code + " but was: " + this);
+ }
+ }
+
+ /**
+ * Parse the given raw event into {@link NativeDaemonEvent} instance.
+ *
+ * @throws IllegalArgumentException when line doesn't match format expected
+ * from native side.
+ */
+ public static NativeDaemonEvent parseRawEvent(String rawEvent) {
+ final int splitIndex = rawEvent.indexOf(' ');
+ if (splitIndex == -1) {
+ throw new IllegalArgumentException("unable to find ' ' separator");
+ }
+
+ final int code;
+ try {
+ code = Integer.parseInt(rawEvent.substring(0, splitIndex));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("problem parsing code", e);
+ }
+
+ final String message = rawEvent.substring(splitIndex + 1);
+ return new NativeDaemonEvent(code, message, rawEvent);
+ }
+
+ /**
+ * Filter the given {@link NativeDaemonEvent} list, returning
+ * {@link #getMessage()} for any events matching the requested code.
+ */
+ public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
+ final ArrayList<String> result = Lists.newArrayList();
+ for (NativeDaemonEvent event : events) {
+ if (event.getCode() == matchCode) {
+ result.add(event.getMessage());
+ }
+ }
+ return result.toArray(new String[result.size()]);
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index f660520..eeb7fec 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -71,7 +71,7 @@
import android.net.NetworkInfo;
import android.net.NetworkState;
import android.net.NetworkStats;
-import android.net.NetworkStats.NonMonotonicException;
+import android.net.NetworkStats.NonMonotonicObserver;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Binder;
@@ -268,6 +268,11 @@
}
public void systemReady() {
+ if (!isBandwidthControlEnabled()) {
+ Slog.w(TAG, "bandwidth controls disabled, unable to track stats");
+ return;
+ }
+
synchronized (mStatsLock) {
// read historical network stats from disk, since policy service
// might need them right away. we delay loading detailed UID stats
@@ -543,7 +548,14 @@
// TODO: switch to data layer stats once kernel exports
// for now, read network layer stats and flatten across all ifaces
- final NetworkStats networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid);
+ final long token = Binder.clearCallingIdentity();
+ final NetworkStats networkLayer;
+ try {
+ networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
final NetworkStats dataLayer = new NetworkStats(
networkLayer.getElapsedRealtime(), networkLayer.size());
@@ -1539,6 +1551,30 @@
}
}
+ private StatsObserver mStatsObserver = new StatsObserver();
+
+ private class StatsObserver implements NonMonotonicObserver {
+ private String mCurrentType;
+
+ public void setCurrentType(String type) {
+ mCurrentType = type;
+ }
+
+ /** {@inheritDoc} */
+ public void foundNonMonotonic(
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
+ Log.w(TAG, "found non-monotonic values; saving to dropbox");
+
+ // record error for debugging
+ final StringBuilder builder = new StringBuilder();
+ builder.append("found non-monotonic " + mCurrentType + " values at left[" + leftIndex
+ + "] - right[" + rightIndex + "]\n");
+ builder.append("left=").append(left).append('\n');
+ builder.append("right=").append(right).append('\n');
+ mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
+ }
+ }
+
/**
* Return the delta between two {@link NetworkStats} snapshots, where {@code
* before} can be {@code null}.
@@ -1546,27 +1582,8 @@
private NetworkStats computeStatsDelta(
NetworkStats before, NetworkStats current, boolean collectStale, String type) {
if (before != null) {
- try {
- return current.subtract(before, false);
- } catch (NonMonotonicException e) {
- Log.w(TAG, "found non-monotonic values; saving to dropbox");
-
- // record error for debugging
- final StringBuilder builder = new StringBuilder();
- builder.append("found non-monotonic " + type + " values at left[" + e.leftIndex
- + "] - right[" + e.rightIndex + "]\n");
- builder.append("left=").append(e.left).append('\n');
- builder.append("right=").append(e.right).append('\n');
- mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
-
- try {
- // return clamped delta to help recover
- return current.subtract(before, true);
- } catch (NonMonotonicException e1) {
- Log.wtf(TAG, "found non-monotonic values; returning empty delta", e1);
- return new NetworkStats(0L, 10);
- }
- }
+ mStatsObserver.setCurrentType(type);
+ return NetworkStats.subtract(current, before, mStatsObserver);
} else if (collectStale) {
// caller is okay collecting stale stats for first call.
return current;
@@ -1646,6 +1663,15 @@
return telephony.getSubscriberId();
}
+ private boolean isBandwidthControlEnabled() {
+ try {
+ return mNetworkManager.isBandwidthControlEnabled();
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ return false;
+ }
+ }
+
/**
* Key uniquely identifying a {@link NetworkStatsHistory} for a UID.
*/
diff --git a/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
new file mode 100644
index 0000000..275d807
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.android.server.NativeDaemonConnector.appendEscaped;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * Tests for {@link NativeDaemonConnector}.
+ */
+@MediumTest
+public class NativeDaemonConnectorTest extends AndroidTestCase {
+ private static final String TAG = "NativeDaemonConnectorTest";
+
+ public void testArgumentNormal() throws Exception {
+ final StringBuilder builder = new StringBuilder();
+
+ builder.setLength(0);
+ appendEscaped(builder, "");
+ assertEquals("", builder.toString());
+
+ builder.setLength(0);
+ appendEscaped(builder, "foo");
+ assertEquals("foo", builder.toString());
+
+ builder.setLength(0);
+ appendEscaped(builder, "foo\"bar");
+ assertEquals("foo\\\"bar", builder.toString());
+
+ builder.setLength(0);
+ appendEscaped(builder, "foo\\bar\\\"baz");
+ assertEquals("foo\\\\bar\\\\\\\"baz", builder.toString());
+ }
+
+ public void testArgumentWithSpaces() throws Exception {
+ final StringBuilder builder = new StringBuilder();
+
+ builder.setLength(0);
+ appendEscaped(builder, "foo bar");
+ assertEquals("\"foo bar\"", builder.toString());
+
+ builder.setLength(0);
+ appendEscaped(builder, "foo\"bar\\baz foo");
+ assertEquals("\"foo\\\"bar\\\\baz foo\"", builder.toString());
+ }
+
+ public void testArgumentWithUtf() throws Exception {
+ final StringBuilder builder = new StringBuilder();
+
+ builder.setLength(0);
+ appendEscaped(builder, "caf\u00E9 c\u00F6ffee");
+ assertEquals("\"caf\u00E9 c\u00F6ffee\"", builder.toString());
+ }
+}