NativeDaemonConnectorpublic final class NativeDaemonConnector extends Object implements Runnable, Watchdog.Monitor, Handler.CallbackGeneric connector class for interfacing with a native daemon which uses the
{@code libsysutils} FrameworkListener protocol. |
Fields Summary |
---|
private static final boolean | LOGD | private static final boolean | VDBG | private final String | TAG | private String | mSocket | private OutputStream | mOutputStream | private android.util.LocalLog | mLocalLog | private final ResponseQueue | mResponseQueue | private final PowerManager.WakeLock | mWakeLock | private final android.os.Looper | mLooper | private INativeDaemonConnectorCallbacks | mCallbacks | private android.os.Handler | mCallbackHandler | private AtomicInteger | mSequenceNumber | private static final int | DEFAULT_TIMEOUT | private static final long | WARN_EXECUTE_DELAY_MS | private final Object | mDaemonLockLock held whenever communicating with native daemon. | private final int | BUFFER_SIZE |
Constructors Summary |
---|
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl)
this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
FgThread.get().getLooper());
| NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl, android.os.Looper looper)
mCallbacks = callbacks;
mSocket = socket;
mResponseQueue = new ResponseQueue(responseQueueSize);
mWakeLock = wl;
if (mWakeLock != null) {
mWakeLock.setReferenceCounted(true);
}
mLooper = looper;
mSequenceNumber = new AtomicInteger(0);
TAG = logTag != null ? logTag : "NativeDaemonConnector";
mLocalLog = new LocalLog(maxLogSize);
|
Methods Summary |
---|
static void | appendEscaped(java.lang.StringBuilder builder, java.lang.String arg)Append the given argument to {@link StringBuilder}, escaping as needed,
and surrounding with quotes when it contains spaces.
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 android.net.LocalSocketAddress | determineSocketAddress()
// If we're testing, set up a socket in a namespace that's accessible to test code.
// In order to ensure that unprivileged apps aren't able to impersonate native daemons on
// production devices, even if said native daemons ill-advisedly pick a socket name that
// starts with __test__, only allow this on debug builds.
if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
return new LocalSocketAddress(mSocket);
} else {
return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
}
| public void | dump(java.io.FileDescriptor fd, java.io.PrintWriter pw, java.lang.String[] args)
mLocalLog.dump(fd, pw, args);
pw.println();
mResponseQueue.dump(fd, pw, args);
| public NativeDaemonEvent[] | execute(int timeout, java.lang.String cmd, java.lang.Object args)Issue the given command to the native daemon and return any {@linke
NativeDaemonEvent@isClassContinue()} responses, including the final
terminal response. Note that the timeout does not count time in deep
sleep. Any arguments must be separated from base command so they can be
properly escaped.
final long startTime = SystemClock.elapsedRealtime();
final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
final StringBuilder rawBuilder = new StringBuilder();
final StringBuilder logBuilder = new StringBuilder();
final int sequenceNumber = mSequenceNumber.incrementAndGet();
makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
final String rawCmd = rawBuilder.toString();
final String logCmd = logBuilder.toString();
log("SND -> {" + logCmd + "}");
synchronized (mDaemonLock) {
if (mOutputStream == null) {
throw new NativeDaemonConnectorException("missing output stream");
} else {
try {
mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new NativeDaemonConnectorException("problem sending command", e);
}
}
}
NativeDaemonEvent event = null;
do {
event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
if (event == null) {
loge("timed-out waiting for response to " + logCmd);
throw new NativeDaemonFailureException(logCmd, event);
}
if (VDBG) log("RMV <- {" + event + "}");
events.add(event);
} while (event.isClassContinue());
final long endTime = SystemClock.elapsedRealtime();
if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
}
if (event.isClassClientError()) {
throw new NativeDaemonArgumentException(logCmd, event);
}
if (event.isClassServerError()) {
throw new NativeDaemonFailureException(logCmd, event);
}
return events.toArray(new NativeDaemonEvent[events.size()]);
| public NativeDaemonEvent | execute(com.android.server.NativeDaemonConnector$Command cmd)Issue the given command to the native daemon and return a single expected
response.
return execute(cmd.mCmd, cmd.mArguments.toArray());
| public NativeDaemonEvent | execute(java.lang.String cmd, java.lang.Object args)Issue the given command to the native daemon and return a single expected
response. Any arguments must be separated from base command so they can
be properly escaped.
final NativeDaemonEvent[] events = executeForList(cmd, args);
if (events.length != 1) {
throw new NativeDaemonConnectorException(
"Expected exactly one response, but received " + events.length);
}
return events[0];
| public NativeDaemonEvent[] | executeForList(com.android.server.NativeDaemonConnector$Command cmd)Issue the given command to the native daemon and return any
{@link NativeDaemonEvent#isClassContinue()} responses, including the
final terminal response.
return executeForList(cmd.mCmd, cmd.mArguments.toArray());
| public NativeDaemonEvent[] | executeForList(java.lang.String cmd, java.lang.Object args)Issue the given command to the native daemon and return any
{@link NativeDaemonEvent#isClassContinue()} responses, including the
final terminal response. Any arguments must be separated from base
command so they can be properly escaped.
return execute(DEFAULT_TIMEOUT, cmd, args);
| public boolean | handleMessage(android.os.Message msg)
String event = (String) msg.obj;
try {
if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
log(String.format("Unhandled event '%s'", event));
}
} catch (Exception e) {
loge("Error handling '" + event + "': " + e);
} finally {
if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
mWakeLock.release();
}
}
return true;
| private void | listenToSocket()
LocalSocket socket = null;
try {
socket = new LocalSocket();
LocalSocketAddress address = determineSocketAddress();
socket.connect(address);
InputStream inputStream = socket.getInputStream();
synchronized (mDaemonLock) {
mOutputStream = socket.getOutputStream();
}
mCallbacks.onDaemonConnected();
byte[] buffer = new byte[BUFFER_SIZE];
int start = 0;
while (true) {
int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
if (count < 0) {
loge("got " + count + " reading with start = " + start);
break;
}
// Add our starting point to the count and reset the start.
count += start;
start = 0;
for (int i = 0; i < count; i++) {
if (buffer[i] == 0) {
// Note - do not log this raw message since it may contain
// sensitive data
final String rawEvent = new String(
buffer, start, i - start, StandardCharsets.UTF_8);
boolean releaseWl = false;
try {
final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
rawEvent);
log("RCV <- {" + event + "}");
if (event.isClassUnsolicited()) {
// TODO: migrate to sending NativeDaemonEvent instances
if (mCallbacks.onCheckHoldWakeLock(event.getCode())
&& mWakeLock != null) {
mWakeLock.acquire();
releaseWl = true;
}
if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
event.getCode(), event.getRawEvent()))) {
releaseWl = false;
}
} else {
mResponseQueue.add(event.getCmdNumber(), event);
}
} catch (IllegalArgumentException e) {
log("Problem parsing message " + e);
} finally {
if (releaseWl) {
mWakeLock.acquire();
}
}
start = i + 1;
}
}
if (start == 0) {
log("RCV incomplete");
}
// We should end at the amount we read. If not, compact then
// buffer and read again.
if (start != count) {
final int remaining = BUFFER_SIZE - start;
System.arraycopy(buffer, start, buffer, 0, remaining);
start = remaining;
} else {
start = 0;
}
}
} catch (IOException ex) {
loge("Communications error: " + ex);
throw ex;
} finally {
synchronized (mDaemonLock) {
if (mOutputStream != null) {
try {
loge("closing stream for " + mSocket);
mOutputStream.close();
} catch (IOException e) {
loge("Failed closing output stream: " + e);
}
mOutputStream = null;
}
}
try {
if (socket != null) {
socket.close();
}
} catch (IOException ex) {
loge("Failed closing socket: " + ex);
}
}
| private void | log(java.lang.String logstring)
if (LOGD) Slog.d(TAG, logstring);
mLocalLog.log(logstring);
| private void | loge(java.lang.String logstring)
Slog.e(TAG, logstring);
mLocalLog.log(logstring);
| static void | makeCommand(java.lang.StringBuilder rawBuilder, java.lang.StringBuilder logBuilder, int sequenceNumber, java.lang.String cmd, java.lang.Object args)Make command for daemon, escaping arguments as needed.
if (cmd.indexOf('\0") >= 0) {
throw new IllegalArgumentException("Unexpected command: " + cmd);
}
if (cmd.indexOf(' ") >= 0) {
throw new IllegalArgumentException("Arguments must be separate from command");
}
rawBuilder.append(sequenceNumber).append(' ").append(cmd);
logBuilder.append(sequenceNumber).append(' ").append(cmd);
for (Object arg : args) {
final String argString = String.valueOf(arg);
if (argString.indexOf('\0") >= 0) {
throw new IllegalArgumentException("Unexpected argument: " + arg);
}
rawBuilder.append(' ");
logBuilder.append(' ");
appendEscaped(rawBuilder, argString);
if (arg instanceof SensitiveArg) {
logBuilder.append("[scrubbed]");
} else {
appendEscaped(logBuilder, argString);
}
}
rawBuilder.append('\0");
| public void | monitor(){@inheritDoc}
synchronized (mDaemonLock) { }
| public void | run()
mCallbackHandler = new Handler(mLooper, this);
while (true) {
try {
listenToSocket();
} catch (Exception e) {
loge("Error in NativeDaemonConnector: " + e);
SystemClock.sleep(5000);
}
}
|
|