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