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 
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl)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 
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl, Looper looper)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      */
setDebug(boolean debug)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      */
uptimeMillisInt()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      */
setWarnIfHeld(Object warnIfHeld)127     public void setWarnIfHeld(Object warnIfHeld) {
128         Preconditions.checkState(mWarnIfHeld == null);
129         mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
130     }
131 
132     @Override
run()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 
isShuttingDown()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
handleMessage(Message msg)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 
determineSocketAddress()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 
listenToSocket()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 
SensitiveArg(Object arg)312         public SensitiveArg(Object arg) {
313             mArg = arg;
314         }
315 
316         @Override
toString()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
makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber, String cmd, Object... args)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      */
waitForCallbacks()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      */
execute(Command cmd)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      */
execute(String cmd, Object... args)404     public NativeDaemonEvent execute(String cmd, Object... args)
405             throws NativeDaemonConnectorException {
406         return execute(DEFAULT_TIMEOUT, cmd, args);
407     }
408 
execute(long timeoutMs, String cmd, Object... args)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      */
executeForList(Command cmd)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      */
executeForList(String cmd, Object... args)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      */
executeForList(long timeoutMs, String cmd, Object... args)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
appendEscaped(StringBuilder builder, String arg)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 {
NativeDaemonArgumentException(String command, NativeDaemonEvent event)551         public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
552             super(command, event);
553         }
554 
555         @Override
rethrowAsParcelableException()556         public IllegalArgumentException rethrowAsParcelableException() {
557             throw new IllegalArgumentException(getMessage(), this);
558         }
559     }
560 
561     private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
NativeDaemonFailureException(String command, NativeDaemonEvent event)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 
Command(String cmd, Object... args)575         public Command(String cmd, Object... args) {
576             mCmd = cmd;
577             for (Object arg : args) {
578                 appendArg(arg);
579             }
580         }
581 
appendArg(Object arg)582         public Command appendArg(Object arg) {
583             mArguments.add(arg);
584             return this;
585         }
586     }
587 
588     /** {@inheritDoc} */
monitor()589     public void monitor() {
590         synchronized (mDaemonLock) { }
591     }
592 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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 
log(String logstring)599     private void log(String logstring) {
600         if (mDebug) Slog.d(TAG, logstring);
601         mLocalLog.log(logstring);
602     }
603 
loge(String logstring)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 
PendingCmd(int cmdNum, String logCmd)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 
ResponseQueue(int maxCount)644         ResponseQueue(int maxCount) {
645             mPendingCmds = new LinkedList<PendingCmd>();
646             mMaxCount = maxCount;
647         }
648 
add(int cmdNum, NativeDaemonEvent response)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
remove(int cmdNum, long timeoutMs, String logCmd)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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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