blob: 92af9a9e6af6b9a81e12a8bbbb53e645096807ae [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 Sharkey31c6e482011-11-18 17:09:01 -080028import com.google.android.collect.Lists;
29
Robert Greenwalt470fd722012-01-18 12:51:15 -080030import java.nio.charset.Charsets;
31import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080032import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080035import java.io.PrintWriter;
San Mehat67bd2cd2010-01-12 12:18:49 -080036import java.util.ArrayList;
Robert Greenwalt470007f2012-02-07 11:36:55 -080037import java.util.concurrent.atomic.AtomicInteger;
Robert Greenwaltef215992012-06-05 11:48:40 -070038import java.util.concurrent.ArrayBlockingQueue;
39import java.util.concurrent.BlockingQueue;
40import java.util.concurrent.TimeUnit;
Robert Greenwalt470007f2012-02-07 11:36:55 -080041import java.util.LinkedList;
San Mehat67bd2cd2010-01-12 12:18:49 -080042
43/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080044 * Generic connector class for interfacing with a native daemon which uses the
45 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080046 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070047final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Jeff Sharkey31c6e482011-11-18 17:09:01 -080048 private static final boolean LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080049
Jeff Sharkey31c6e482011-11-18 17:09:01 -080050 private final String TAG;
51
52 private String mSocket;
53 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080054 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080055
Robert Greenwalt470007f2012-02-07 11:36:55 -080056 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080057
San Mehat67bd2cd2010-01-12 12:18:49 -080058 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080059 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080060
Robert Greenwalt470007f2012-02-07 11:36:55 -080061 private AtomicInteger mSequenceNumber;
62
63 private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070064 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080065
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070066 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080067 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070068
Kenny Root961aa8c2010-03-22 18:02:45 -070069 private final int BUFFER_SIZE = 4096;
70
Jeff Sharkey31c6e482011-11-18 17:09:01 -080071 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Robert Greenwalt470fd722012-01-18 12:51:15 -080072 int responseQueueSize, String logTag, int maxLogSize) {
San Mehat67bd2cd2010-01-12 12:18:49 -080073 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080074 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080075 mResponseQueue = new ResponseQueue(responseQueueSize);
76 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -080077 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -080078 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -080079 }
80
Chia-chi Yehe5750a32011-08-03 14:42:11 -070081 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080082 public void run() {
Chia-chi Yehe5750a32011-08-03 14:42:11 -070083 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
84 thread.start();
85 mCallbackHandler = new Handler(thread.getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080086
87 while (true) {
88 try {
89 listenToSocket();
90 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -080091 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -080092 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080093 }
94 }
95 }
96
Chia-chi Yehe5750a32011-08-03 14:42:11 -070097 @Override
98 public boolean handleMessage(Message msg) {
99 String event = (String) msg.obj;
100 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700101 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800102 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700103 }
104 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800105 loge("Error handling '" + event + "': " + e);
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700106 }
107 return true;
108 }
109
San Mehat4c27e0e2010-01-29 05:22:17 -0800110 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700111 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800112
113 try {
114 socket = new LocalSocket();
115 LocalSocketAddress address = new LocalSocketAddress(mSocket,
116 LocalSocketAddress.Namespace.RESERVED);
117
118 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800119
120 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800121 synchronized (mDaemonLock) {
122 mOutputStream = socket.getOutputStream();
123 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800124
anga030bc882011-02-01 14:10:25 +0100125 mCallbacks.onDaemonConnected();
126
Kenny Root961aa8c2010-03-22 18:02:45 -0700127 byte[] buffer = new byte[BUFFER_SIZE];
128 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800129
130 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700131 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800132 if (count < 0) {
133 loge("got " + count + " reading with start = " + start);
134 break;
135 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800136
Kenny Root12da9d72010-09-02 22:18:14 -0700137 // Add our starting point to the count and reset the start.
138 count += start;
139 start = 0;
140
San Mehat67bd2cd2010-01-12 12:18:49 -0800141 for (int i = 0; i < count; i++) {
142 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800143 final String rawEvent = new String(
144 buffer, start, i - start, Charsets.UTF_8);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800145 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800146
San Mehat67bd2cd2010-01-12 12:18:49 -0800147 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800148 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
149 rawEvent);
150 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800151 // TODO: migrate to sending NativeDaemonEvent instances
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800152 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
153 event.getCode(), event.getRawEvent()));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800154 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800155 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800156 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800157 } catch (IllegalArgumentException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800158 log("Problem parsing message: " + rawEvent + " - " + e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800159 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800160
San Mehat67bd2cd2010-01-12 12:18:49 -0800161 start = i + 1;
162 }
163 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800164 if (start == 0) {
165 final String rawEvent = new String(buffer, start, count, Charsets.UTF_8);
166 log("RCV incomplete <- {" + rawEvent + "}");
167 }
Kenny Root12da9d72010-09-02 22:18:14 -0700168
169 // We should end at the amount we read. If not, compact then
170 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700171 if (start != count) {
172 final int remaining = BUFFER_SIZE - start;
173 System.arraycopy(buffer, start, buffer, 0, remaining);
174 start = remaining;
175 } else {
176 start = 0;
177 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800178 }
179 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800180 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800181 throw ex;
182 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700183 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800184 if (mOutputStream != null) {
185 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800186 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800187 mOutputStream.close();
188 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800189 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800190 }
191 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800192 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800193 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800194
San Mehat4c27e0e2010-01-29 05:22:17 -0800195 try {
196 if (socket != null) {
197 socket.close();
198 }
199 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800200 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800201 }
202 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800203 }
204
San Mehat67bd2cd2010-01-12 12:18:49 -0800205 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800206 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800207 */
Robert Greenwaltd1925982012-03-12 15:37:40 -0700208 private void makeCommand(StringBuilder builder, String cmd, Object... args)
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700209 throws NativeDaemonConnectorException {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800210 // TODO: eventually enforce that cmd doesn't contain arguments
211 if (cmd.indexOf('\0') >= 0) {
212 throw new IllegalArgumentException("unexpected command: " + cmd);
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700213 }
214
Robert Greenwaltd1925982012-03-12 15:37:40 -0700215 builder.append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800216 for (Object arg : args) {
217 final String argString = String.valueOf(arg);
218 if (argString.indexOf('\0') >= 0) {
219 throw new IllegalArgumentException("unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700220 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800221
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800222 builder.append(' ');
223 appendEscaped(builder, argString);
224 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800225 }
San Mehatdeba6932010-01-20 15:14:31 -0800226
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700227 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800228 * Issue the given command to the native daemon and return a single expected
229 * response.
230 *
231 * @throws NativeDaemonConnectorException when problem communicating with
232 * native daemon, or if the response matches
233 * {@link NativeDaemonEvent#isClassClientError()} or
234 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800235 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800236 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
237 return execute(cmd.mCmd, cmd.mArguments.toArray());
238 }
239
240 /**
241 * Issue the given command to the native daemon and return a single expected
242 * response.
243 *
244 * @throws NativeDaemonConnectorException when problem communicating with
245 * native daemon, or if the response matches
246 * {@link NativeDaemonEvent#isClassClientError()} or
247 * {@link NativeDaemonEvent#isClassServerError()}.
248 */
249 public NativeDaemonEvent execute(String cmd, Object... args)
250 throws NativeDaemonConnectorException {
251 final NativeDaemonEvent[] events = executeForList(cmd, args);
252 if (events.length != 1) {
253 throw new NativeDaemonConnectorException(
254 "Expected exactly one response, but received " + events.length);
255 }
256 return events[0];
257 }
258
259 /**
260 * Issue the given command to the native daemon and return any
261 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
262 * final terminal response.
263 *
264 * @throws NativeDaemonConnectorException when problem communicating with
265 * native daemon, or if the response matches
266 * {@link NativeDaemonEvent#isClassClientError()} or
267 * {@link NativeDaemonEvent#isClassServerError()}.
268 */
269 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
270 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
271 }
272
273 /**
274 * Issue the given command to the native daemon and return any
275 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
276 * final terminal response.
277 *
278 * @throws NativeDaemonConnectorException when problem communicating with
279 * native daemon, or if the response matches
280 * {@link NativeDaemonEvent#isClassClientError()} or
281 * {@link NativeDaemonEvent#isClassServerError()}.
282 */
283 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800284 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800285 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800286 }
San Mehatdeba6932010-01-20 15:14:31 -0800287
Robert Greenwalt470007f2012-02-07 11:36:55 -0800288 /**
289 * Issue the given command to the native daemon and return any
290 * {@linke NativeDaemonEvent@isClassContinue()} responses, including the
291 * final terminal response. Note that the timeout does not count time in
292 * deep sleep.
293 *
294 * @throws NativeDaemonConnectorException when problem communicating with
295 * native daemon, or if the response matches
296 * {@link NativeDaemonEvent#isClassClientError()} or
297 * {@link NativeDaemonEvent#isClassServerError()}.
298 */
299 public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800300 throws NativeDaemonConnectorException {
301 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700302
303 final int sequenceNumber = mSequenceNumber.incrementAndGet();
304 final StringBuilder cmdBuilder =
305 new StringBuilder(Integer.toString(sequenceNumber)).append(' ');
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700306 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700307
308 makeCommand(cmdBuilder, cmd, args);
309
310 final String logCmd = cmdBuilder.toString(); /* includes cmdNum, cmd, args */
311 log("SND -> {" + logCmd + "}");
312
313 cmdBuilder.append('\0');
314 final String sentCmd = cmdBuilder.toString(); /* logCmd + \0 */
315
316 synchronized (mDaemonLock) {
317 if (mOutputStream == null) {
318 throw new NativeDaemonConnectorException("missing output stream");
319 } else {
320 try {
321 mOutputStream.write(sentCmd.getBytes(Charsets.UTF_8));
322 } catch (IOException e) {
323 throw new NativeDaemonConnectorException("problem sending command", e);
324 }
325 }
326 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800327
328 NativeDaemonEvent event = null;
329 do {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700330 event = mResponseQueue.remove(sequenceNumber, timeout, sentCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800331 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700332 loge("timed-out waiting for response to " + logCmd);
333 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800334 }
Robert Greenwaltb5aff3f2012-05-15 17:26:57 -0700335 log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800336 events.add(event);
337 } while (event.isClassContinue());
338
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700339 final long endTime = SystemClock.elapsedRealtime();
340 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
341 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
342 }
343
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800344 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700345 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800346 }
347 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700348 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800349 }
350
351 return events.toArray(new NativeDaemonEvent[events.size()]);
352 }
353
354 /**
355 * Issue a command to the native daemon and return the raw responses.
356 *
357 * @deprecated callers should move to {@link #execute(String, Object...)}
358 * which returns parsed {@link NativeDaemonEvent}.
359 */
360 @Deprecated
361 public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
362 final ArrayList<String> rawEvents = Lists.newArrayList();
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800363 final NativeDaemonEvent[] events = executeForList(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800364 for (NativeDaemonEvent event : events) {
365 rawEvents.add(event.getRawEvent());
366 }
367 return rawEvents;
368 }
369
370 /**
371 * Issues a list command and returns the cooked list of all
372 * {@link NativeDaemonEvent#getMessage()} which match requested code.
373 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800374 @Deprecated
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800375 public String[] doListCommand(String cmd, int expectedCode)
376 throws NativeDaemonConnectorException {
377 final ArrayList<String> list = Lists.newArrayList();
378
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800379 final NativeDaemonEvent[] events = executeForList(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800380 for (int i = 0; i < events.length - 1; i++) {
381 final NativeDaemonEvent event = events[i];
382 final int code = event.getCode();
383 if (code == expectedCode) {
384 list.add(event.getMessage());
385 } else {
San Mehat4c27e0e2010-01-29 05:22:17 -0800386 throw new NativeDaemonConnectorException(
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800387 "unexpected list response " + code + " instead of " + expectedCode);
San Mehatdeba6932010-01-20 15:14:31 -0800388 }
389 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800390
391 final NativeDaemonEvent finalEvent = events[events.length - 1];
392 if (!finalEvent.isClassOk()) {
393 throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
394 }
395
396 return list.toArray(new String[list.size()]);
397 }
398
399 /**
400 * Append the given argument to {@link StringBuilder}, escaping as needed,
401 * and surrounding with quotes when it contains spaces.
402 */
403 // @VisibleForTesting
404 static void appendEscaped(StringBuilder builder, String arg) {
405 final boolean hasSpaces = arg.indexOf(' ') >= 0;
406 if (hasSpaces) {
407 builder.append('"');
408 }
409
410 final int length = arg.length();
411 for (int i = 0; i < length; i++) {
412 final char c = arg.charAt(i);
413
414 if (c == '"') {
415 builder.append("\\\"");
416 } else if (c == '\\') {
417 builder.append("\\\\");
418 } else {
419 builder.append(c);
420 }
421 }
422
423 if (hasSpaces) {
424 builder.append('"');
425 }
426 }
427
428 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
429 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
430 super(command, event);
431 }
432
433 @Override
434 public IllegalArgumentException rethrowAsParcelableException() {
435 throw new IllegalArgumentException(getMessage(), this);
436 }
437 }
438
439 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
440 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
441 super(command, event);
442 }
San Mehatdeba6932010-01-20 15:14:31 -0800443 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700444
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800445 /**
446 * Command builder that handles argument list building.
447 */
448 public static class Command {
449 private String mCmd;
450 private ArrayList<Object> mArguments = Lists.newArrayList();
451
452 public Command(String cmd, Object... args) {
453 mCmd = cmd;
454 for (Object arg : args) {
455 appendArg(arg);
456 }
457 }
458
459 public Command appendArg(Object arg) {
460 mArguments.add(arg);
461 return this;
462 }
463 }
464
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700465 /** {@inheritDoc} */
466 public void monitor() {
467 synchronized (mDaemonLock) { }
468 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800469
470 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
471 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800472 pw.println();
473 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800474 }
475
476 private void log(String logstring) {
477 if (LOGD) Slog.d(TAG, logstring);
478 mLocalLog.log(logstring);
479 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800480
481 private void loge(String logstring) {
482 Slog.e(TAG, logstring);
483 mLocalLog.log(logstring);
484 }
485
486 private static class ResponseQueue {
487
Robert Greenwaltef215992012-06-05 11:48:40 -0700488 private static class PendingCmd {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800489 public int cmdNum;
Robert Greenwaltef215992012-06-05 11:48:40 -0700490 public BlockingQueue<NativeDaemonEvent> responses =
491 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800492 public String request;
Robert Greenwaltef215992012-06-05 11:48:40 -0700493
494 // The availableResponseCount member is used to track when we can remove this
495 // instance from the ResponseQueue.
496 // This is used under the protection of a sync of the mPendingCmds object.
497 // A positive value means we've had more writers retreive this object while
498 // a negative value means we've had more readers. When we've had an equal number
499 // (it goes to zero) we can remove this object from the mPendingCmds list.
500 // Note that we may have more responses for this command (and more readers
501 // coming), but that would result in a new PendingCmd instance being created
502 // and added with the same cmdNum.
503 // Also note that when this goes to zero it just means a parity of readers and
504 // writers have retrieved this object - not that they are done using it. The
505 // responses queue may well have more responses yet to be read or may get more
506 // responses added to it. But all those readers/writers have retreived and
507 // hold references to this instance already so it can be removed from
508 // mPendingCmds queue.
509 public int availableResponseCount;
510 public PendingCmd(int c, String r) {cmdNum = c; request = r;}
Robert Greenwalt470007f2012-02-07 11:36:55 -0800511 }
512
Robert Greenwaltef215992012-06-05 11:48:40 -0700513 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800514 private int mMaxCount;
515
516 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700517 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800518 mMaxCount = maxCount;
519 }
520
521 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700522 PendingCmd found = null;
523 synchronized (mPendingCmds) {
524 for (PendingCmd pendingCmd : mPendingCmds) {
525 if (pendingCmd.cmdNum == cmdNum) {
526 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800527 break;
528 }
529 }
530 if (found == null) {
531 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700532 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800533 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700534 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800535 " >= " + mMaxCount);
536 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700537 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800538 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700539 "Removing request: " + pendingCmd.request + " (" +
540 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800541 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700542 found = new PendingCmd(cmdNum, null);
543 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800544 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700545 found.availableResponseCount++;
546 // if a matching remove call has already retrieved this we can remove this
547 // instance from our list
548 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800549 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700550 try {
551 found.responses.put(response);
552 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800553 }
554
555 // note that the timeout does not count time in deep sleep. If you don't want
556 // the device to sleep, hold a wakelock
557 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String origCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700558 PendingCmd found = null;
559 synchronized (mPendingCmds) {
560 for (PendingCmd pendingCmd : mPendingCmds) {
561 if (pendingCmd.cmdNum == cmdNum) {
562 found = pendingCmd;
563 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800564 }
565 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700566 if (found == null) {
567 found = new PendingCmd(cmdNum, origCmd);
568 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800569 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700570 found.availableResponseCount--;
571 // if a matching add call has already retrieved this we can remove this
572 // instance from our list
573 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800574 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700575 NativeDaemonEvent result = null;
576 try {
577 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
578 } catch (InterruptedException e) {}
579 if (result == null) {
580 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
581 }
582 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800583 }
584
585 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
586 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700587 synchronized (mPendingCmds) {
588 for (PendingCmd pendingCmd : mPendingCmds) {
589 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.request);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800590 }
591 }
592 }
593 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800594}