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