Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import android.net.LocalSocket;
     20 import android.net.LocalSocketAddress;
     21 import android.os.Build;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import android.os.Message;
     25 import android.os.PowerManager;
     26 import android.os.SystemClock;
     27 import android.util.LocalLog;
     28 import android.util.Slog;
     29 
     30 import com.android.internal.annotations.VisibleForTesting;
     31 import com.android.internal.util.Preconditions;
     32 import com.google.android.collect.Lists;
     33 
     34 import java.io.FileDescriptor;
     35 import java.io.IOException;
     36 import java.io.InputStream;
     37 import java.io.OutputStream;
     38 import java.io.PrintWriter;
     39 import java.nio.charset.StandardCharsets;
     40 import java.util.ArrayList;
     41 import java.util.concurrent.atomic.AtomicInteger;
     42 import java.util.concurrent.ArrayBlockingQueue;
     43 import java.util.concurrent.BlockingQueue;
     44 import java.util.concurrent.CountDownLatch;
     45 import java.util.concurrent.TimeUnit;
     46 import java.util.LinkedList;
     47 
     48 /**
     49  * Generic connector class for interfacing with a native daemon which uses the
     50  * {@code libsysutils} FrameworkListener protocol.
     51  */
     52 final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
     53     private final static boolean VDBG = false;
     54 
     55     private final String TAG;
     56 
     57     private String mSocket;
     58     private OutputStream mOutputStream;
     59     private LocalLog mLocalLog;
     60 
     61     private volatile boolean mDebug = false;
     62     private volatile Object mWarnIfHeld;
     63 
     64     private final ResponseQueue mResponseQueue;
     65 
     66     private final PowerManager.WakeLock mWakeLock;
     67 
     68     private final Looper mLooper;
     69 
     70     private INativeDaemonConnectorCallbacks mCallbacks;
     71     private Handler mCallbackHandler;
     72 
     73     private AtomicInteger mSequenceNumber;
     74 
     75     private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
     76     private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
     77 
     78     /** Lock held whenever communicating with native daemon. */
     79     private final Object mDaemonLock = new Object();
     80 
     81     private final int BUFFER_SIZE = 4096;
     82 
     83     NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
     84             int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
     85         this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
     86                 FgThread.get().getLooper());
     87     }
     88 
     89     NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
     90             int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
     91             Looper looper) {
     92         mCallbacks = callbacks;
     93         mSocket = socket;
     94         mResponseQueue = new ResponseQueue(responseQueueSize);
     95         mWakeLock = wl;
     96         if (mWakeLock != null) {
     97             mWakeLock.setReferenceCounted(true);
     98         }
     99         mLooper = looper;
    100         mSequenceNumber = new AtomicInteger(0);
    101         TAG = logTag != null ? logTag : "NativeDaemonConnector";
    102         mLocalLog = new LocalLog(maxLogSize);
    103     }
    104 
    105     /**
    106      * Enable Set debugging mode, which causes messages to also be written to both
    107      * {@link Slog} in addition to internal log.
    108      */
    109     public void setDebug(boolean debug) {
    110         mDebug = debug;
    111     }
    112 
    113     /**
    114      * Like SystemClock.uptimeMillis, except truncated to an int so it will fit in a message arg.
    115      * Inaccurate across 49.7 days of uptime, but only used for debugging.
    116      */
    117     private int uptimeMillisInt() {
    118         return (int) SystemClock.uptimeMillis() & Integer.MAX_VALUE;
    119     }
    120 
    121     /**
    122      * Yell loudly if someone tries making future {@link #execute(Command)}
    123      * calls while holding a lock on the given object.
    124      */
    125     public void setWarnIfHeld(Object warnIfHeld) {
    126         Preconditions.checkState(mWarnIfHeld == null);
    127         mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
    128     }
    129 
    130     @Override
    131     public void run() {
    132         mCallbackHandler = new Handler(mLooper, this);
    133 
    134         while (true) {
    135             try {
    136                 listenToSocket();
    137             } catch (Exception e) {
    138                 loge("Error in NativeDaemonConnector: " + e);
    139                 SystemClock.sleep(5000);
    140             }
    141         }
    142     }
    143 
    144     @Override
    145     public boolean handleMessage(Message msg) {
    146         final String event = (String) msg.obj;
    147         final int start = uptimeMillisInt();
    148         final int sent = msg.arg1;
    149         try {
    150             if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
    151                 log(String.format("Unhandled event '%s'", event));
    152             }
    153         } catch (Exception e) {
    154             loge("Error handling '" + event + "': " + e);
    155         } finally {
    156             if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
    157                 mWakeLock.release();
    158             }
    159             final int end = uptimeMillisInt();
    160             if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
    161                 loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
    162             }
    163             if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
    164                 loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
    165             }
    166         }
    167         return true;
    168     }
    169 
    170     private LocalSocketAddress determineSocketAddress() {
    171         // If we're testing, set up a socket in a namespace that's accessible to test code.
    172         // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
    173         // production devices, even if said native daemons ill-advisedly pick a socket name that
    174         // starts with __test__, only allow this on debug builds.
    175         if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
    176             return new LocalSocketAddress(mSocket);
    177         } else {
    178             return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
    179         }
    180     }
    181 
    182     private void listenToSocket() throws IOException {
    183         LocalSocket socket = null;
    184 
    185         try {
    186             socket = new LocalSocket();
    187             LocalSocketAddress address = determineSocketAddress();
    188 
    189             socket.connect(address);
    190 
    191             InputStream inputStream = socket.getInputStream();
    192             synchronized (mDaemonLock) {
    193                 mOutputStream = socket.getOutputStream();
    194             }
    195 
    196             mCallbacks.onDaemonConnected();
    197 
    198             FileDescriptor[] fdList = null;
    199             byte[] buffer = new byte[BUFFER_SIZE];
    200             int start = 0;
    201 
    202             while (true) {
    203                 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
    204                 if (count < 0) {
    205                     loge("got " + count + " reading with start = " + start);
    206                     break;
    207                 }
    208                 fdList = socket.getAncillaryFileDescriptors();
    209 
    210                 // Add our starting point to the count and reset the start.
    211                 count += start;
    212                 start = 0;
    213 
    214                 for (int i = 0; i < count; i++) {
    215                     if (buffer[i] == 0) {
    216                         // Note - do not log this raw message since it may contain
    217                         // sensitive data
    218                         final String rawEvent = new String(
    219                                 buffer, start, i - start, StandardCharsets.UTF_8);
    220 
    221                         boolean releaseWl = false;
    222                         try {
    223                             final NativeDaemonEvent event =
    224                                     NativeDaemonEvent.parseRawEvent(rawEvent, fdList);
    225 
    226                             log("RCV <- {" + event + "}");
    227 
    228                             if (event.isClassUnsolicited()) {
    229                                 // TODO: migrate to sending NativeDaemonEvent instances
    230                                 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
    231                                         && mWakeLock != null) {
    232                                     mWakeLock.acquire();
    233                                     releaseWl = true;
    234                                 }
    235                                 Message msg = mCallbackHandler.obtainMessage(
    236                                         event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
    237                                 if (mCallbackHandler.sendMessage(msg)) {
    238                                     releaseWl = false;
    239                                 }
    240                             } else {
    241                                 mResponseQueue.add(event.getCmdNumber(), event);
    242                             }
    243                         } catch (IllegalArgumentException e) {
    244                             log("Problem parsing message " + e);
    245                         } finally {
    246                             if (releaseWl) {
    247                                 mWakeLock.release();
    248                             }
    249                         }
    250 
    251                         start = i + 1;
    252                     }
    253                 }
    254 
    255                 if (start == 0) {
    256                     log("RCV incomplete");
    257                 }
    258 
    259                 // We should end at the amount we read. If not, compact then
    260                 // buffer and read again.
    261                 if (start != count) {
    262                     final int remaining = BUFFER_SIZE - start;
    263                     System.arraycopy(buffer, start, buffer, 0, remaining);
    264                     start = remaining;
    265                 } else {
    266                     start = 0;
    267                 }
    268             }
    269         } catch (IOException ex) {
    270             loge("Communications error: " + ex);
    271             throw ex;
    272         } finally {
    273             synchronized (mDaemonLock) {
    274                 if (mOutputStream != null) {
    275                     try {
    276                         loge("closing stream for " + mSocket);
    277                         mOutputStream.close();
    278                     } catch (IOException e) {
    279                         loge("Failed closing output stream: " + e);
    280                     }
    281                     mOutputStream = null;
    282                 }
    283             }
    284 
    285             try {
    286                 if (socket != null) {
    287                     socket.close();
    288                 }
    289             } catch (IOException ex) {
    290                 loge("Failed closing socket: " + ex);
    291             }
    292         }
    293     }
    294 
    295     /**
    296      * Wrapper around argument that indicates it's sensitive and shouldn't be
    297      * logged.
    298      */
    299     public static class SensitiveArg {
    300         private final Object mArg;
    301 
    302         public SensitiveArg(Object arg) {
    303             mArg = arg;
    304         }
    305 
    306         @Override
    307         public String toString() {
    308             return String.valueOf(mArg);
    309         }
    310     }
    311 
    312     /**
    313      * Make command for daemon, escaping arguments as needed.
    314      */
    315     @VisibleForTesting
    316     static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
    317             String cmd, Object... args) {
    318         if (cmd.indexOf('\0') >= 0) {
    319             throw new IllegalArgumentException("Unexpected command: " + cmd);
    320         }
    321         if (cmd.indexOf(' ') >= 0) {
    322             throw new IllegalArgumentException("Arguments must be separate from command");
    323         }
    324 
    325         rawBuilder.append(sequenceNumber).append(' ').append(cmd);
    326         logBuilder.append(sequenceNumber).append(' ').append(cmd);
    327         for (Object arg : args) {
    328             final String argString = String.valueOf(arg);
    329             if (argString.indexOf('\0') >= 0) {
    330                 throw new IllegalArgumentException("Unexpected argument: " + arg);
    331             }
    332 
    333             rawBuilder.append(' ');
    334             logBuilder.append(' ');
    335 
    336             appendEscaped(rawBuilder, argString);
    337             if (arg instanceof SensitiveArg) {
    338                 logBuilder.append("[scrubbed]");
    339             } else {
    340                 appendEscaped(logBuilder, argString);
    341             }
    342         }
    343 
    344         rawBuilder.append('\0');
    345     }
    346 
    347     /**
    348      * Method that waits until all asychronous notifications sent by the native daemon have
    349      * been processed. This method must not be called on the notification thread or an
    350      * exception will be thrown.
    351      */
    352     public void waitForCallbacks() {
    353         if (Thread.currentThread() == mLooper.getThread()) {
    354             throw new IllegalStateException("Must not call this method on callback thread");
    355         }
    356 
    357         final CountDownLatch latch = new CountDownLatch(1);
    358         mCallbackHandler.post(new Runnable() {
    359             @Override
    360             public void run() {
    361                 latch.countDown();
    362             }
    363         });
    364         try {
    365             latch.await();
    366         } catch (InterruptedException e) {
    367             Slog.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
    368         }
    369     }
    370 
    371     /**
    372      * Issue the given command to the native daemon and return a single expected
    373      * response.
    374      *
    375      * @throws NativeDaemonConnectorException when problem communicating with
    376      *             native daemon, or if the response matches
    377      *             {@link NativeDaemonEvent#isClassClientError()} or
    378      *             {@link NativeDaemonEvent#isClassServerError()}.
    379      */
    380     public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
    381         return execute(cmd.mCmd, cmd.mArguments.toArray());
    382     }
    383 
    384     /**
    385      * Issue the given command to the native daemon and return a single expected
    386      * response. Any arguments must be separated from base command so they can
    387      * be properly escaped.
    388      *
    389      * @throws NativeDaemonConnectorException when problem communicating with
    390      *             native daemon, or if the response matches
    391      *             {@link NativeDaemonEvent#isClassClientError()} or
    392      *             {@link NativeDaemonEvent#isClassServerError()}.
    393      */
    394     public NativeDaemonEvent execute(String cmd, Object... args)
    395             throws NativeDaemonConnectorException {
    396         return execute(DEFAULT_TIMEOUT, cmd, args);
    397     }
    398 
    399     public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
    400             throws NativeDaemonConnectorException {
    401         final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
    402         if (events.length != 1) {
    403             throw new NativeDaemonConnectorException(
    404                     "Expected exactly one response, but received " + events.length);
    405         }
    406         return events[0];
    407     }
    408 
    409     /**
    410      * Issue the given command to the native daemon and return any
    411      * {@link NativeDaemonEvent#isClassContinue()} responses, including the
    412      * final terminal response.
    413      *
    414      * @throws NativeDaemonConnectorException when problem communicating with
    415      *             native daemon, or if the response matches
    416      *             {@link NativeDaemonEvent#isClassClientError()} or
    417      *             {@link NativeDaemonEvent#isClassServerError()}.
    418      */
    419     public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
    420         return executeForList(cmd.mCmd, cmd.mArguments.toArray());
    421     }
    422 
    423     /**
    424      * Issue the given command to the native daemon and return any
    425      * {@link NativeDaemonEvent#isClassContinue()} responses, including the
    426      * final terminal response. Any arguments must be separated from base
    427      * command so they can be properly escaped.
    428      *
    429      * @throws NativeDaemonConnectorException when problem communicating with
    430      *             native daemon, or if the response matches
    431      *             {@link NativeDaemonEvent#isClassClientError()} or
    432      *             {@link NativeDaemonEvent#isClassServerError()}.
    433      */
    434     public NativeDaemonEvent[] executeForList(String cmd, Object... args)
    435             throws NativeDaemonConnectorException {
    436         return executeForList(DEFAULT_TIMEOUT, cmd, args);
    437     }
    438 
    439     /**
    440      * Issue the given command to the native daemon and return any {@linke
    441      * NativeDaemonEvent@isClassContinue()} responses, including the final
    442      * terminal response. Note that the timeout does not count time in deep
    443      * sleep. Any arguments must be separated from base command so they can be
    444      * properly escaped.
    445      *
    446      * @throws NativeDaemonConnectorException when problem communicating with
    447      *             native daemon, or if the response matches
    448      *             {@link NativeDaemonEvent#isClassClientError()} or
    449      *             {@link NativeDaemonEvent#isClassServerError()}.
    450      */
    451     public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
    452             throws NativeDaemonConnectorException {
    453         if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
    454             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
    455                     + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
    456         }
    457 
    458         final long startTime = SystemClock.elapsedRealtime();
    459 
    460         final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
    461 
    462         final StringBuilder rawBuilder = new StringBuilder();
    463         final StringBuilder logBuilder = new StringBuilder();
    464         final int sequenceNumber = mSequenceNumber.incrementAndGet();
    465 
    466         makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
    467 
    468         final String rawCmd = rawBuilder.toString();
    469         final String logCmd = logBuilder.toString();
    470 
    471         log("SND -> {" + logCmd + "}");
    472 
    473         synchronized (mDaemonLock) {
    474             if (mOutputStream == null) {
    475                 throw new NativeDaemonConnectorException("missing output stream");
    476             } else {
    477                 try {
    478                     mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
    479                 } catch (IOException e) {
    480                     throw new NativeDaemonConnectorException("problem sending command", e);
    481                 }
    482             }
    483         }
    484 
    485         NativeDaemonEvent event = null;
    486         do {
    487             event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
    488             if (event == null) {
    489                 loge("timed-out waiting for response to " + logCmd);
    490                 throw new NativeDaemonTimeoutException(logCmd, event);
    491             }
    492             if (VDBG) log("RMV <- {" + event + "}");
    493             events.add(event);
    494         } while (event.isClassContinue());
    495 
    496         final long endTime = SystemClock.elapsedRealtime();
    497         if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
    498             loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
    499         }
    500 
    501         if (event.isClassClientError()) {
    502             throw new NativeDaemonArgumentException(logCmd, event);
    503         }
    504         if (event.isClassServerError()) {
    505             throw new NativeDaemonFailureException(logCmd, event);
    506         }
    507 
    508         return events.toArray(new NativeDaemonEvent[events.size()]);
    509     }
    510 
    511     /**
    512      * Append the given argument to {@link StringBuilder}, escaping as needed,
    513      * and surrounding with quotes when it contains spaces.
    514      */
    515     @VisibleForTesting
    516     static void appendEscaped(StringBuilder builder, String arg) {
    517         final boolean hasSpaces = arg.indexOf(' ') >= 0;
    518         if (hasSpaces) {
    519             builder.append('"');
    520         }
    521 
    522         final int length = arg.length();
    523         for (int i = 0; i < length; i++) {
    524             final char c = arg.charAt(i);
    525 
    526             if (c == '"') {
    527                 builder.append("\\\"");
    528             } else if (c == '\\') {
    529                 builder.append("\\\\");
    530             } else {
    531                 builder.append(c);
    532             }
    533         }
    534 
    535         if (hasSpaces) {
    536             builder.append('"');
    537         }
    538     }
    539 
    540     private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
    541         public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
    542             super(command, event);
    543         }
    544 
    545         @Override
    546         public IllegalArgumentException rethrowAsParcelableException() {
    547             throw new IllegalArgumentException(getMessage(), this);
    548         }
    549     }
    550 
    551     private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
    552         public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
    553             super(command, event);
    554         }
    555     }
    556 
    557     /**
    558      * Command builder that handles argument list building. Any arguments must
    559      * be separated from base command so they can be properly escaped.
    560      */
    561     public static class Command {
    562         private String mCmd;
    563         private ArrayList<Object> mArguments = Lists.newArrayList();
    564 
    565         public Command(String cmd, Object... args) {
    566             mCmd = cmd;
    567             for (Object arg : args) {
    568                 appendArg(arg);
    569             }
    570         }
    571 
    572         public Command appendArg(Object arg) {
    573             mArguments.add(arg);
    574             return this;
    575         }
    576     }
    577 
    578     /** {@inheritDoc} */
    579     public void monitor() {
    580         synchronized (mDaemonLock) { }
    581     }
    582 
    583     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    584         mLocalLog.dump(fd, pw, args);
    585         pw.println();
    586         mResponseQueue.dump(fd, pw, args);
    587     }
    588 
    589     private void log(String logstring) {
    590         if (mDebug) Slog.d(TAG, logstring);
    591         mLocalLog.log(logstring);
    592     }
    593 
    594     private void loge(String logstring) {
    595         Slog.e(TAG, logstring);
    596         mLocalLog.log(logstring);
    597     }
    598 
    599     private static class ResponseQueue {
    600 
    601         private static class PendingCmd {
    602             public final int cmdNum;
    603             public final String logCmd;
    604 
    605             public BlockingQueue<NativeDaemonEvent> responses =
    606                     new ArrayBlockingQueue<NativeDaemonEvent>(10);
    607 
    608             // The availableResponseCount member is used to track when we can remove this
    609             // instance from the ResponseQueue.
    610             // This is used under the protection of a sync of the mPendingCmds object.
    611             // A positive value means we've had more writers retreive this object while
    612             // a negative value means we've had more readers.  When we've had an equal number
    613             // (it goes to zero) we can remove this object from the mPendingCmds list.
    614             // Note that we may have more responses for this command (and more readers
    615             // coming), but that would result in a new PendingCmd instance being created
    616             // and added with the same cmdNum.
    617             // Also note that when this goes to zero it just means a parity of readers and
    618             // writers have retrieved this object - not that they are done using it.  The
    619             // responses queue may well have more responses yet to be read or may get more
    620             // responses added to it.  But all those readers/writers have retreived and
    621             // hold references to this instance already so it can be removed from
    622             // mPendingCmds queue.
    623             public int availableResponseCount;
    624 
    625             public PendingCmd(int cmdNum, String logCmd) {
    626                 this.cmdNum = cmdNum;
    627                 this.logCmd = logCmd;
    628             }
    629         }
    630 
    631         private final LinkedList<PendingCmd> mPendingCmds;
    632         private int mMaxCount;
    633 
    634         ResponseQueue(int maxCount) {
    635             mPendingCmds = new LinkedList<PendingCmd>();
    636             mMaxCount = maxCount;
    637         }
    638 
    639         public void add(int cmdNum, NativeDaemonEvent response) {
    640             PendingCmd found = null;
    641             synchronized (mPendingCmds) {
    642                 for (PendingCmd pendingCmd : mPendingCmds) {
    643                     if (pendingCmd.cmdNum == cmdNum) {
    644                         found = pendingCmd;
    645                         break;
    646                     }
    647                 }
    648                 if (found == null) {
    649                     // didn't find it - make sure our queue isn't too big before adding
    650                     while (mPendingCmds.size() >= mMaxCount) {
    651                         Slog.e("NativeDaemonConnector.ResponseQueue",
    652                                 "more buffered than allowed: " + mPendingCmds.size() +
    653                                 " >= " + mMaxCount);
    654                         // let any waiter timeout waiting for this
    655                         PendingCmd pendingCmd = mPendingCmds.remove();
    656                         Slog.e("NativeDaemonConnector.ResponseQueue",
    657                                 "Removing request: " + pendingCmd.logCmd + " (" +
    658                                 pendingCmd.cmdNum + ")");
    659                     }
    660                     found = new PendingCmd(cmdNum, null);
    661                     mPendingCmds.add(found);
    662                 }
    663                 found.availableResponseCount++;
    664                 // if a matching remove call has already retrieved this we can remove this
    665                 // instance from our list
    666                 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
    667             }
    668             try {
    669                 found.responses.put(response);
    670             } catch (InterruptedException e) { }
    671         }
    672 
    673         // note that the timeout does not count time in deep sleep.  If you don't want
    674         // the device to sleep, hold a wakelock
    675         public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
    676             PendingCmd found = null;
    677             synchronized (mPendingCmds) {
    678                 for (PendingCmd pendingCmd : mPendingCmds) {
    679                     if (pendingCmd.cmdNum == cmdNum) {
    680                         found = pendingCmd;
    681                         break;
    682                     }
    683                 }
    684                 if (found == null) {
    685                     found = new PendingCmd(cmdNum, logCmd);
    686                     mPendingCmds.add(found);
    687                 }
    688                 found.availableResponseCount--;
    689                 // if a matching add call has already retrieved this we can remove this
    690                 // instance from our list
    691                 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
    692             }
    693             NativeDaemonEvent result = null;
    694             try {
    695                 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
    696             } catch (InterruptedException e) {}
    697             if (result == null) {
    698                 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
    699             }
    700             return result;
    701         }
    702 
    703         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    704             pw.println("Pending requests:");
    705             synchronized (mPendingCmds) {
    706                 for (PendingCmd pendingCmd : mPendingCmds) {
    707                     pw.println("  Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
    708                 }
    709             }
    710         }
    711     }
    712 }
    713