blob: 47840e0882ca52bb90f04f59dab4851766515666 [file] [log] [blame]
San Mehat67bd2cd2010-01-12 12:18:49 -08001/*
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
17package com.android.server;
18
San Mehat67bd2cd2010-01-12 12:18:49 -080019import android.net.LocalSocket;
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070020import android.net.LocalSocketAddress;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070021import android.os.Handler;
22import android.os.HandlerThread;
23import android.os.Message;
San Mehat67bd2cd2010-01-12 12:18:49 -080024import android.os.SystemClock;
Robert Greenwalt470fd722012-01-18 12:51:15 -080025import android.util.LocalLog;
Joe Onorato8a9b2202010-02-26 18:56:32 -080026import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080027
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080028import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080029import com.google.android.collect.Lists;
30
Robert Greenwalt470fd722012-01-18 12:51:15 -080031import java.nio.charset.Charsets;
32import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080033import java.io.IOException;
34import java.io.InputStream;
35import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080036import java.io.PrintWriter;
San Mehat67bd2cd2010-01-12 12:18:49 -080037import java.util.ArrayList;
Robert Greenwalt470007f2012-02-07 11:36:55 -080038import java.util.concurrent.atomic.AtomicInteger;
Robert Greenwaltef215992012-06-05 11:48:40 -070039import java.util.concurrent.ArrayBlockingQueue;
40import java.util.concurrent.BlockingQueue;
41import java.util.concurrent.TimeUnit;
Robert Greenwalt470007f2012-02-07 11:36:55 -080042import java.util.LinkedList;
San Mehat67bd2cd2010-01-12 12:18:49 -080043
44/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080045 * Generic connector class for interfacing with a native daemon which uses the
46 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080047 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070048final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Jeff Sharkey31c6e482011-11-18 17:09:01 -080049 private static final boolean LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080050
Jeff Sharkey31c6e482011-11-18 17:09:01 -080051 private final String TAG;
52
53 private String mSocket;
54 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080055 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080056
Robert Greenwalt470007f2012-02-07 11:36:55 -080057 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080058
San Mehat67bd2cd2010-01-12 12:18:49 -080059 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080060 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080061
Robert Greenwalt470007f2012-02-07 11:36:55 -080062 private AtomicInteger mSequenceNumber;
63
64 private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070065 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080066
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070067 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080068 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070069
Kenny Root961aa8c2010-03-22 18:02:45 -070070 private final int BUFFER_SIZE = 4096;
71
Jeff Sharkey31c6e482011-11-18 17:09:01 -080072 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Robert Greenwalt470fd722012-01-18 12:51:15 -080073 int responseQueueSize, String logTag, int maxLogSize) {
San Mehat67bd2cd2010-01-12 12:18:49 -080074 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080075 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080076 mResponseQueue = new ResponseQueue(responseQueueSize);
77 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -080078 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -080079 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -080080 }
81
Chia-chi Yehe5750a32011-08-03 14:42:11 -070082 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080083 public void run() {
Chia-chi Yehe5750a32011-08-03 14:42:11 -070084 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
85 thread.start();
86 mCallbackHandler = new Handler(thread.getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080087
88 while (true) {
89 try {
90 listenToSocket();
91 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -080092 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -080093 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080094 }
95 }
96 }
97
Chia-chi Yehe5750a32011-08-03 14:42:11 -070098 @Override
99 public boolean handleMessage(Message msg) {
100 String event = (String) msg.obj;
101 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700102 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800103 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700104 }
105 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800106 loge("Error handling '" + event + "': " + e);
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700107 }
108 return true;
109 }
110
San Mehat4c27e0e2010-01-29 05:22:17 -0800111 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700112 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800113
114 try {
115 socket = new LocalSocket();
116 LocalSocketAddress address = new LocalSocketAddress(mSocket,
117 LocalSocketAddress.Namespace.RESERVED);
118
119 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800120
121 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800122 synchronized (mDaemonLock) {
123 mOutputStream = socket.getOutputStream();
124 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800125
anga030bc882011-02-01 14:10:25 +0100126 mCallbacks.onDaemonConnected();
127
Kenny Root961aa8c2010-03-22 18:02:45 -0700128 byte[] buffer = new byte[BUFFER_SIZE];
129 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800130
131 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700132 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800133 if (count < 0) {
134 loge("got " + count + " reading with start = " + start);
135 break;
136 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800137
Kenny Root12da9d72010-09-02 22:18:14 -0700138 // Add our starting point to the count and reset the start.
139 count += start;
140 start = 0;
141
San Mehat67bd2cd2010-01-12 12:18:49 -0800142 for (int i = 0; i < count; i++) {
143 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800144 final String rawEvent = new String(
145 buffer, start, i - start, Charsets.UTF_8);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800146 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800147
San Mehat67bd2cd2010-01-12 12:18:49 -0800148 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800149 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
150 rawEvent);
151 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800152 // TODO: migrate to sending NativeDaemonEvent instances
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800153 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
154 event.getCode(), event.getRawEvent()));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800155 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800156 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800157 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800158 } catch (IllegalArgumentException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800159 log("Problem parsing message: " + rawEvent + " - " + e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800160 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800161
San Mehat67bd2cd2010-01-12 12:18:49 -0800162 start = i + 1;
163 }
164 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800165 if (start == 0) {
166 final String rawEvent = new String(buffer, start, count, Charsets.UTF_8);
167 log("RCV incomplete <- {" + rawEvent + "}");
168 }
Kenny Root12da9d72010-09-02 22:18:14 -0700169
170 // We should end at the amount we read. If not, compact then
171 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700172 if (start != count) {
173 final int remaining = BUFFER_SIZE - start;
174 System.arraycopy(buffer, start, buffer, 0, remaining);
175 start = remaining;
176 } else {
177 start = 0;
178 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800179 }
180 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800181 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800182 throw ex;
183 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700184 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800185 if (mOutputStream != null) {
186 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800187 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800188 mOutputStream.close();
189 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800190 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800191 }
192 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800193 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800194 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800195
San Mehat4c27e0e2010-01-29 05:22:17 -0800196 try {
197 if (socket != null) {
198 socket.close();
199 }
200 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800201 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800202 }
203 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800204 }
205
San Mehat67bd2cd2010-01-12 12:18:49 -0800206 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700207 * Wrapper around argument that indicates it's sensitive and shouldn't be
208 * logged.
209 */
210 public static class SensitiveArg {
211 private final Object mArg;
212
213 public SensitiveArg(Object arg) {
214 mArg = arg;
215 }
216
217 @Override
218 public String toString() {
219 return String.valueOf(mArg);
220 }
221 }
222
223 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800224 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800225 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700226 @VisibleForTesting
227 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
228 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800229 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800230 throw new IllegalArgumentException("Unexpected command: " + cmd);
231 }
232 if (cmd.indexOf(' ') >= 0) {
233 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700234 }
235
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700236 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
237 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800238 for (Object arg : args) {
239 final String argString = String.valueOf(arg);
240 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800241 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700242 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800243
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700244 rawBuilder.append(' ');
245 logBuilder.append(' ');
246
247 appendEscaped(rawBuilder, argString);
248 if (arg instanceof SensitiveArg) {
249 logBuilder.append("[scrubbed]");
250 } else {
251 appendEscaped(logBuilder, argString);
252 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800253 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700254
255 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800256 }
San Mehatdeba6932010-01-20 15:14:31 -0800257
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700258 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800259 * Issue the given command to the native daemon and return a single expected
260 * response.
261 *
262 * @throws NativeDaemonConnectorException when problem communicating with
263 * native daemon, or if the response matches
264 * {@link NativeDaemonEvent#isClassClientError()} or
265 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800266 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800267 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
268 return execute(cmd.mCmd, cmd.mArguments.toArray());
269 }
270
271 /**
272 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800273 * response. Any arguments must be separated from base command so they can
274 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800275 *
276 * @throws NativeDaemonConnectorException when problem communicating with
277 * native daemon, or if the response matches
278 * {@link NativeDaemonEvent#isClassClientError()} or
279 * {@link NativeDaemonEvent#isClassServerError()}.
280 */
281 public NativeDaemonEvent execute(String cmd, Object... args)
282 throws NativeDaemonConnectorException {
283 final NativeDaemonEvent[] events = executeForList(cmd, args);
284 if (events.length != 1) {
285 throw new NativeDaemonConnectorException(
286 "Expected exactly one response, but received " + events.length);
287 }
288 return events[0];
289 }
290
291 /**
292 * Issue the given command to the native daemon and return any
293 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
294 * final terminal response.
295 *
296 * @throws NativeDaemonConnectorException when problem communicating with
297 * native daemon, or if the response matches
298 * {@link NativeDaemonEvent#isClassClientError()} or
299 * {@link NativeDaemonEvent#isClassServerError()}.
300 */
301 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
302 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
303 }
304
305 /**
306 * Issue the given command to the native daemon and return any
307 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800308 * final terminal response. Any arguments must be separated from base
309 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800310 *
311 * @throws NativeDaemonConnectorException when problem communicating with
312 * native daemon, or if the response matches
313 * {@link NativeDaemonEvent#isClassClientError()} or
314 * {@link NativeDaemonEvent#isClassServerError()}.
315 */
316 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800317 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800318 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800319 }
San Mehatdeba6932010-01-20 15:14:31 -0800320
Robert Greenwalt470007f2012-02-07 11:36:55 -0800321 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800322 * Issue the given command to the native daemon and return any {@linke
323 * NativeDaemonEvent@isClassContinue()} responses, including the final
324 * terminal response. Note that the timeout does not count time in deep
325 * sleep. Any arguments must be separated from base command so they can be
326 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800327 *
328 * @throws NativeDaemonConnectorException when problem communicating with
329 * native daemon, or if the response matches
330 * {@link NativeDaemonEvent#isClassClientError()} or
331 * {@link NativeDaemonEvent#isClassServerError()}.
332 */
333 public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800334 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700335 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700336
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700337 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700338
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700339 final StringBuilder rawBuilder = new StringBuilder();
340 final StringBuilder logBuilder = new StringBuilder();
341 final int sequenceNumber = mSequenceNumber.incrementAndGet();
342
343 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
344
345 final String rawCmd = rawBuilder.toString();
346 final String logCmd = logBuilder.toString();
347
Robert Greenwaltd1925982012-03-12 15:37:40 -0700348 log("SND -> {" + logCmd + "}");
349
Robert Greenwaltd1925982012-03-12 15:37:40 -0700350 synchronized (mDaemonLock) {
351 if (mOutputStream == null) {
352 throw new NativeDaemonConnectorException("missing output stream");
353 } else {
354 try {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700355 mOutputStream.write(rawCmd.getBytes(Charsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700356 } catch (IOException e) {
357 throw new NativeDaemonConnectorException("problem sending command", e);
358 }
359 }
360 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800361
362 NativeDaemonEvent event = null;
363 do {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700364 event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800365 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700366 loge("timed-out waiting for response to " + logCmd);
367 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800368 }
Robert Greenwaltb5aff3f2012-05-15 17:26:57 -0700369 log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800370 events.add(event);
371 } while (event.isClassContinue());
372
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700373 final long endTime = SystemClock.elapsedRealtime();
374 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
375 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
376 }
377
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800378 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700379 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800380 }
381 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700382 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800383 }
384
385 return events.toArray(new NativeDaemonEvent[events.size()]);
386 }
387
388 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800389 * Append the given argument to {@link StringBuilder}, escaping as needed,
390 * and surrounding with quotes when it contains spaces.
391 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800392 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800393 static void appendEscaped(StringBuilder builder, String arg) {
394 final boolean hasSpaces = arg.indexOf(' ') >= 0;
395 if (hasSpaces) {
396 builder.append('"');
397 }
398
399 final int length = arg.length();
400 for (int i = 0; i < length; i++) {
401 final char c = arg.charAt(i);
402
403 if (c == '"') {
404 builder.append("\\\"");
405 } else if (c == '\\') {
406 builder.append("\\\\");
407 } else {
408 builder.append(c);
409 }
410 }
411
412 if (hasSpaces) {
413 builder.append('"');
414 }
415 }
416
417 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
418 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
419 super(command, event);
420 }
421
422 @Override
423 public IllegalArgumentException rethrowAsParcelableException() {
424 throw new IllegalArgumentException(getMessage(), this);
425 }
426 }
427
428 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
429 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
430 super(command, event);
431 }
San Mehatdeba6932010-01-20 15:14:31 -0800432 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700433
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800434 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800435 * Command builder that handles argument list building. Any arguments must
436 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800437 */
438 public static class Command {
439 private String mCmd;
440 private ArrayList<Object> mArguments = Lists.newArrayList();
441
442 public Command(String cmd, Object... args) {
443 mCmd = cmd;
444 for (Object arg : args) {
445 appendArg(arg);
446 }
447 }
448
449 public Command appendArg(Object arg) {
450 mArguments.add(arg);
451 return this;
452 }
453 }
454
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700455 /** {@inheritDoc} */
456 public void monitor() {
457 synchronized (mDaemonLock) { }
458 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800459
460 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
461 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800462 pw.println();
463 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800464 }
465
466 private void log(String logstring) {
467 if (LOGD) Slog.d(TAG, logstring);
468 mLocalLog.log(logstring);
469 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800470
471 private void loge(String logstring) {
472 Slog.e(TAG, logstring);
473 mLocalLog.log(logstring);
474 }
475
476 private static class ResponseQueue {
477
Robert Greenwaltef215992012-06-05 11:48:40 -0700478 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700479 public final int cmdNum;
480 public final String logCmd;
481
Robert Greenwaltef215992012-06-05 11:48:40 -0700482 public BlockingQueue<NativeDaemonEvent> responses =
483 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700484
485 // The availableResponseCount member is used to track when we can remove this
486 // instance from the ResponseQueue.
487 // This is used under the protection of a sync of the mPendingCmds object.
488 // A positive value means we've had more writers retreive this object while
489 // a negative value means we've had more readers. When we've had an equal number
490 // (it goes to zero) we can remove this object from the mPendingCmds list.
491 // Note that we may have more responses for this command (and more readers
492 // coming), but that would result in a new PendingCmd instance being created
493 // and added with the same cmdNum.
494 // Also note that when this goes to zero it just means a parity of readers and
495 // writers have retrieved this object - not that they are done using it. The
496 // responses queue may well have more responses yet to be read or may get more
497 // responses added to it. But all those readers/writers have retreived and
498 // hold references to this instance already so it can be removed from
499 // mPendingCmds queue.
500 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700501
502 public PendingCmd(int cmdNum, String logCmd) {
503 this.cmdNum = cmdNum;
504 this.logCmd = logCmd;
505 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800506 }
507
Robert Greenwaltef215992012-06-05 11:48:40 -0700508 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800509 private int mMaxCount;
510
511 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700512 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800513 mMaxCount = maxCount;
514 }
515
516 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700517 PendingCmd found = null;
518 synchronized (mPendingCmds) {
519 for (PendingCmd pendingCmd : mPendingCmds) {
520 if (pendingCmd.cmdNum == cmdNum) {
521 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800522 break;
523 }
524 }
525 if (found == null) {
526 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700527 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800528 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700529 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800530 " >= " + mMaxCount);
531 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700532 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800533 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700534 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700535 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800536 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700537 found = new PendingCmd(cmdNum, null);
538 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800539 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700540 found.availableResponseCount++;
541 // if a matching remove call has already retrieved this we can remove this
542 // instance from our list
543 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800544 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700545 try {
546 found.responses.put(response);
547 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800548 }
549
550 // note that the timeout does not count time in deep sleep. If you don't want
551 // the device to sleep, hold a wakelock
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700552 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700553 PendingCmd found = null;
554 synchronized (mPendingCmds) {
555 for (PendingCmd pendingCmd : mPendingCmds) {
556 if (pendingCmd.cmdNum == cmdNum) {
557 found = pendingCmd;
558 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800559 }
560 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700561 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700562 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700563 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800564 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700565 found.availableResponseCount--;
566 // if a matching add call has already retrieved this we can remove this
567 // instance from our list
568 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800569 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700570 NativeDaemonEvent result = null;
571 try {
572 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
573 } catch (InterruptedException e) {}
574 if (result == null) {
575 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
576 }
577 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800578 }
579
580 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
581 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700582 synchronized (mPendingCmds) {
583 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700584 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800585 }
586 }
587 }
588 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800589}