blob: a9942e3fd0a23741adab6fcca5e9b24f7ac8af96 [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;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070022import android.os.Message;
San Mehat67bd2cd2010-01-12 12:18:49 -080023import android.os.SystemClock;
Robert Greenwalt470fd722012-01-18 12:51:15 -080024import android.util.LocalLog;
Joe Onorato8a9b2202010-02-26 18:56:32 -080025import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080026
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080027import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080028import com.google.android.collect.Lists;
29
Robert Greenwalt470fd722012-01-18 12:51:15 -080030import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080031import java.io.IOException;
32import java.io.InputStream;
33import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080034import java.io.PrintWriter;
Elliott Hughesd396a442013-06-28 16:24:48 -070035import java.nio.charset.StandardCharsets;
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() {
Dianne Hackborn8d044e82013-04-30 17:24:15 -070083 mCallbackHandler = new Handler(FgThread.get().getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080084
85 while (true) {
86 try {
87 listenToSocket();
88 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -080089 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -080090 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080091 }
92 }
93 }
94
Chia-chi Yehe5750a32011-08-03 14:42:11 -070095 @Override
96 public boolean handleMessage(Message msg) {
97 String event = (String) msg.obj;
98 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -070099 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800100 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700101 }
102 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800103 loge("Error handling '" + event + "': " + e);
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700104 }
105 return true;
106 }
107
San Mehat4c27e0e2010-01-29 05:22:17 -0800108 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700109 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800110
111 try {
112 socket = new LocalSocket();
113 LocalSocketAddress address = new LocalSocketAddress(mSocket,
114 LocalSocketAddress.Namespace.RESERVED);
115
116 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800117
118 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800119 synchronized (mDaemonLock) {
120 mOutputStream = socket.getOutputStream();
121 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800122
anga030bc882011-02-01 14:10:25 +0100123 mCallbacks.onDaemonConnected();
124
Kenny Root961aa8c2010-03-22 18:02:45 -0700125 byte[] buffer = new byte[BUFFER_SIZE];
126 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800127
128 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700129 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800130 if (count < 0) {
131 loge("got " + count + " reading with start = " + start);
132 break;
133 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800134
Kenny Root12da9d72010-09-02 22:18:14 -0700135 // Add our starting point to the count and reset the start.
136 count += start;
137 start = 0;
138
San Mehat67bd2cd2010-01-12 12:18:49 -0800139 for (int i = 0; i < count; i++) {
140 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800141 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700142 buffer, start, i - start, StandardCharsets.UTF_8);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800143 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800144
San Mehat67bd2cd2010-01-12 12:18:49 -0800145 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800146 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
147 rawEvent);
148 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800149 // TODO: migrate to sending NativeDaemonEvent instances
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800150 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
151 event.getCode(), event.getRawEvent()));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800152 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800153 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800154 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800155 } catch (IllegalArgumentException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800156 log("Problem parsing message: " + rawEvent + " - " + e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800157 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800158
San Mehat67bd2cd2010-01-12 12:18:49 -0800159 start = i + 1;
160 }
161 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800162 if (start == 0) {
Elliott Hughesd396a442013-06-28 16:24:48 -0700163 final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800164 log("RCV incomplete <- {" + rawEvent + "}");
165 }
Kenny Root12da9d72010-09-02 22:18:14 -0700166
167 // We should end at the amount we read. If not, compact then
168 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700169 if (start != count) {
170 final int remaining = BUFFER_SIZE - start;
171 System.arraycopy(buffer, start, buffer, 0, remaining);
172 start = remaining;
173 } else {
174 start = 0;
175 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800176 }
177 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800178 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800179 throw ex;
180 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700181 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800182 if (mOutputStream != null) {
183 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800184 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800185 mOutputStream.close();
186 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800187 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800188 }
189 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800190 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800191 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800192
San Mehat4c27e0e2010-01-29 05:22:17 -0800193 try {
194 if (socket != null) {
195 socket.close();
196 }
197 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800198 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800199 }
200 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800201 }
202
San Mehat67bd2cd2010-01-12 12:18:49 -0800203 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700204 * Wrapper around argument that indicates it's sensitive and shouldn't be
205 * logged.
206 */
207 public static class SensitiveArg {
208 private final Object mArg;
209
210 public SensitiveArg(Object arg) {
211 mArg = arg;
212 }
213
214 @Override
215 public String toString() {
216 return String.valueOf(mArg);
217 }
218 }
219
220 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800221 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800222 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700223 @VisibleForTesting
224 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
225 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800226 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800227 throw new IllegalArgumentException("Unexpected command: " + cmd);
228 }
229 if (cmd.indexOf(' ') >= 0) {
230 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700231 }
232
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700233 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
234 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800235 for (Object arg : args) {
236 final String argString = String.valueOf(arg);
237 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800238 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700239 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800240
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700241 rawBuilder.append(' ');
242 logBuilder.append(' ');
243
244 appendEscaped(rawBuilder, argString);
245 if (arg instanceof SensitiveArg) {
246 logBuilder.append("[scrubbed]");
247 } else {
248 appendEscaped(logBuilder, argString);
249 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800250 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700251
252 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800253 }
San Mehatdeba6932010-01-20 15:14:31 -0800254
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700255 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800256 * Issue the given command to the native daemon and return a single expected
257 * response.
258 *
259 * @throws NativeDaemonConnectorException when problem communicating with
260 * native daemon, or if the response matches
261 * {@link NativeDaemonEvent#isClassClientError()} or
262 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800263 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800264 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
265 return execute(cmd.mCmd, cmd.mArguments.toArray());
266 }
267
268 /**
269 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800270 * response. Any arguments must be separated from base command so they can
271 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800272 *
273 * @throws NativeDaemonConnectorException when problem communicating with
274 * native daemon, or if the response matches
275 * {@link NativeDaemonEvent#isClassClientError()} or
276 * {@link NativeDaemonEvent#isClassServerError()}.
277 */
278 public NativeDaemonEvent execute(String cmd, Object... args)
279 throws NativeDaemonConnectorException {
280 final NativeDaemonEvent[] events = executeForList(cmd, args);
281 if (events.length != 1) {
282 throw new NativeDaemonConnectorException(
283 "Expected exactly one response, but received " + events.length);
284 }
285 return events[0];
286 }
287
288 /**
289 * Issue the given command to the native daemon and return any
290 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
291 * final terminal response.
292 *
293 * @throws NativeDaemonConnectorException when problem communicating with
294 * native daemon, or if the response matches
295 * {@link NativeDaemonEvent#isClassClientError()} or
296 * {@link NativeDaemonEvent#isClassServerError()}.
297 */
298 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
299 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
300 }
301
302 /**
303 * Issue the given command to the native daemon and return any
304 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800305 * final terminal response. Any arguments must be separated from base
306 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800307 *
308 * @throws NativeDaemonConnectorException when problem communicating with
309 * native daemon, or if the response matches
310 * {@link NativeDaemonEvent#isClassClientError()} or
311 * {@link NativeDaemonEvent#isClassServerError()}.
312 */
313 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800314 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800315 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800316 }
San Mehatdeba6932010-01-20 15:14:31 -0800317
Robert Greenwalt470007f2012-02-07 11:36:55 -0800318 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800319 * Issue the given command to the native daemon and return any {@linke
320 * NativeDaemonEvent@isClassContinue()} responses, including the final
321 * terminal response. Note that the timeout does not count time in deep
322 * sleep. Any arguments must be separated from base command so they can be
323 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800324 *
325 * @throws NativeDaemonConnectorException when problem communicating with
326 * native daemon, or if the response matches
327 * {@link NativeDaemonEvent#isClassClientError()} or
328 * {@link NativeDaemonEvent#isClassServerError()}.
329 */
330 public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800331 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700332 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700333
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700334 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700335
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700336 final StringBuilder rawBuilder = new StringBuilder();
337 final StringBuilder logBuilder = new StringBuilder();
338 final int sequenceNumber = mSequenceNumber.incrementAndGet();
339
340 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
341
342 final String rawCmd = rawBuilder.toString();
343 final String logCmd = logBuilder.toString();
344
Robert Greenwaltd1925982012-03-12 15:37:40 -0700345 log("SND -> {" + logCmd + "}");
346
Robert Greenwaltd1925982012-03-12 15:37:40 -0700347 synchronized (mDaemonLock) {
348 if (mOutputStream == null) {
349 throw new NativeDaemonConnectorException("missing output stream");
350 } else {
351 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700352 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700353 } catch (IOException e) {
354 throw new NativeDaemonConnectorException("problem sending command", e);
355 }
356 }
357 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800358
359 NativeDaemonEvent event = null;
360 do {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700361 event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800362 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700363 loge("timed-out waiting for response to " + logCmd);
364 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800365 }
Robert Greenwaltb5aff3f2012-05-15 17:26:57 -0700366 log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800367 events.add(event);
368 } while (event.isClassContinue());
369
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700370 final long endTime = SystemClock.elapsedRealtime();
371 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
372 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
373 }
374
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800375 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700376 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800377 }
378 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700379 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800380 }
381
382 return events.toArray(new NativeDaemonEvent[events.size()]);
383 }
384
385 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800386 * Append the given argument to {@link StringBuilder}, escaping as needed,
387 * and surrounding with quotes when it contains spaces.
388 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800389 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800390 static void appendEscaped(StringBuilder builder, String arg) {
391 final boolean hasSpaces = arg.indexOf(' ') >= 0;
392 if (hasSpaces) {
393 builder.append('"');
394 }
395
396 final int length = arg.length();
397 for (int i = 0; i < length; i++) {
398 final char c = arg.charAt(i);
399
400 if (c == '"') {
401 builder.append("\\\"");
402 } else if (c == '\\') {
403 builder.append("\\\\");
404 } else {
405 builder.append(c);
406 }
407 }
408
409 if (hasSpaces) {
410 builder.append('"');
411 }
412 }
413
414 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
415 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
416 super(command, event);
417 }
418
419 @Override
420 public IllegalArgumentException rethrowAsParcelableException() {
421 throw new IllegalArgumentException(getMessage(), this);
422 }
423 }
424
425 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
426 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
427 super(command, event);
428 }
San Mehatdeba6932010-01-20 15:14:31 -0800429 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700430
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800431 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800432 * Command builder that handles argument list building. Any arguments must
433 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800434 */
435 public static class Command {
436 private String mCmd;
437 private ArrayList<Object> mArguments = Lists.newArrayList();
438
439 public Command(String cmd, Object... args) {
440 mCmd = cmd;
441 for (Object arg : args) {
442 appendArg(arg);
443 }
444 }
445
446 public Command appendArg(Object arg) {
447 mArguments.add(arg);
448 return this;
449 }
450 }
451
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700452 /** {@inheritDoc} */
453 public void monitor() {
454 synchronized (mDaemonLock) { }
455 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800456
457 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
458 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800459 pw.println();
460 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800461 }
462
463 private void log(String logstring) {
464 if (LOGD) Slog.d(TAG, logstring);
465 mLocalLog.log(logstring);
466 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800467
468 private void loge(String logstring) {
469 Slog.e(TAG, logstring);
470 mLocalLog.log(logstring);
471 }
472
473 private static class ResponseQueue {
474
Robert Greenwaltef215992012-06-05 11:48:40 -0700475 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700476 public final int cmdNum;
477 public final String logCmd;
478
Robert Greenwaltef215992012-06-05 11:48:40 -0700479 public BlockingQueue<NativeDaemonEvent> responses =
480 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700481
482 // The availableResponseCount member is used to track when we can remove this
483 // instance from the ResponseQueue.
484 // This is used under the protection of a sync of the mPendingCmds object.
485 // A positive value means we've had more writers retreive this object while
486 // a negative value means we've had more readers. When we've had an equal number
487 // (it goes to zero) we can remove this object from the mPendingCmds list.
488 // Note that we may have more responses for this command (and more readers
489 // coming), but that would result in a new PendingCmd instance being created
490 // and added with the same cmdNum.
491 // Also note that when this goes to zero it just means a parity of readers and
492 // writers have retrieved this object - not that they are done using it. The
493 // responses queue may well have more responses yet to be read or may get more
494 // responses added to it. But all those readers/writers have retreived and
495 // hold references to this instance already so it can be removed from
496 // mPendingCmds queue.
497 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700498
499 public PendingCmd(int cmdNum, String logCmd) {
500 this.cmdNum = cmdNum;
501 this.logCmd = logCmd;
502 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800503 }
504
Robert Greenwaltef215992012-06-05 11:48:40 -0700505 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800506 private int mMaxCount;
507
508 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700509 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800510 mMaxCount = maxCount;
511 }
512
513 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700514 PendingCmd found = null;
515 synchronized (mPendingCmds) {
516 for (PendingCmd pendingCmd : mPendingCmds) {
517 if (pendingCmd.cmdNum == cmdNum) {
518 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800519 break;
520 }
521 }
522 if (found == null) {
523 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700524 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800525 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700526 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800527 " >= " + mMaxCount);
528 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700529 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800530 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700531 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700532 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800533 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700534 found = new PendingCmd(cmdNum, null);
535 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800536 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700537 found.availableResponseCount++;
538 // if a matching remove call has already retrieved this we can remove this
539 // instance from our list
540 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800541 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700542 try {
543 found.responses.put(response);
544 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800545 }
546
547 // note that the timeout does not count time in deep sleep. If you don't want
548 // the device to sleep, hold a wakelock
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700549 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700550 PendingCmd found = null;
551 synchronized (mPendingCmds) {
552 for (PendingCmd pendingCmd : mPendingCmds) {
553 if (pendingCmd.cmdNum == cmdNum) {
554 found = pendingCmd;
555 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800556 }
557 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700558 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700559 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700560 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800561 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700562 found.availableResponseCount--;
563 // if a matching add call has already retrieved this we can remove this
564 // instance from our list
565 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800566 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700567 NativeDaemonEvent result = null;
568 try {
569 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
570 } catch (InterruptedException e) {}
571 if (result == null) {
572 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
573 }
574 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800575 }
576
577 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
578 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700579 synchronized (mPendingCmds) {
580 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700581 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800582 }
583 }
584 }
585 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800586}