FileDocCategorySizeDatePackage
NativeDaemonConnector.javaAPI DocAndroid 5.1 API24252Thu Mar 12 22:22:42 GMT 2015com.android.server

NativeDaemonConnector

public final class NativeDaemonConnector extends Object implements Runnable, Watchdog.Monitor, Handler.Callback
Generic 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
mDaemonLock
Lock 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 voidappendEscaped(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.LocalSocketAddressdetermineSocketAddress()

        // 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 voiddump(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.

throws
NativeDaemonConnectorException when problem communicating with native daemon, or if the response matches {@link NativeDaemonEvent#isClassClientError()} or {@link NativeDaemonEvent#isClassServerError()}.

        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 NativeDaemonEventexecute(com.android.server.NativeDaemonConnector$Command cmd)
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()}.

        return execute(cmd.mCmd, cmd.mArguments.toArray());
    
public NativeDaemonEventexecute(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.

throws
NativeDaemonConnectorException when problem communicating with native daemon, or if the response matches {@link NativeDaemonEvent#isClassClientError()} or {@link NativeDaemonEvent#isClassServerError()}.

        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.

throws
NativeDaemonConnectorException when problem communicating with native daemon, or if the response matches {@link NativeDaemonEvent#isClassClientError()} or {@link NativeDaemonEvent#isClassServerError()}.

        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.

throws
NativeDaemonConnectorException when problem communicating with native daemon, or if the response matches {@link NativeDaemonEvent#isClassClientError()} or {@link NativeDaemonEvent#isClassServerError()}.

            return execute(DEFAULT_TIMEOUT, cmd, args);
    
public booleanhandleMessage(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 voidlistenToSocket()

        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 voidlog(java.lang.String logstring)

        if (LOGD) Slog.d(TAG, logstring);
        mLocalLog.log(logstring);
    
private voidloge(java.lang.String logstring)

        Slog.e(TAG, logstring);
        mLocalLog.log(logstring);
    
static voidmakeCommand(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 voidmonitor()
{@inheritDoc}

        synchronized (mDaemonLock) { }
    
public voidrun()

        mCallbackHandler = new Handler(mLooper, this);

        while (true) {
            try {
                listenToSocket();
            } catch (Exception e) {
                loge("Error in NativeDaemonConnector: " + e);
                SystemClock.sleep(5000);
            }
        }