blob: 417d6d81cdf6a4eddbc6ed6a7b8bc885eb809029 [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;
Lorenzo Colitti7421a012013-08-20 22:51:24 +090021import android.os.Build;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070022import android.os.Handler;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070023import 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.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;
Elliott Hughesd396a442013-06-28 16:24:48 -070036import java.nio.charset.StandardCharsets;
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() {
Dianne Hackborn8d044e82013-04-30 17:24:15 -070084 mCallbackHandler = new Handler(FgThread.get().getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080085
86 while (true) {
87 try {
88 listenToSocket();
89 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -080090 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -080091 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080092 }
93 }
94 }
95
Chia-chi Yehe5750a32011-08-03 14:42:11 -070096 @Override
97 public boolean handleMessage(Message msg) {
98 String event = (String) msg.obj;
99 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700100 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800101 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700102 }
103 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800104 loge("Error handling '" + event + "': " + e);
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700105 }
106 return true;
107 }
108
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900109 private LocalSocketAddress determineSocketAddress() {
110 // If we're testing, set up a socket in a namespace that's accessible to test code.
111 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
112 // production devices, even if said native daemons ill-advisedly pick a socket name that
113 // starts with __test__, only allow this on debug builds.
114 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
115 return new LocalSocketAddress(mSocket);
116 } else {
117 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
118 }
119 }
120
San Mehat4c27e0e2010-01-29 05:22:17 -0800121 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700122 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800123
124 try {
125 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900126 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800127
128 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800129
130 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800131 synchronized (mDaemonLock) {
132 mOutputStream = socket.getOutputStream();
133 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800134
anga030bc882011-02-01 14:10:25 +0100135 mCallbacks.onDaemonConnected();
136
Kenny Root961aa8c2010-03-22 18:02:45 -0700137 byte[] buffer = new byte[BUFFER_SIZE];
138 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800139
140 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700141 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800142 if (count < 0) {
143 loge("got " + count + " reading with start = " + start);
144 break;
145 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800146
Kenny Root12da9d72010-09-02 22:18:14 -0700147 // Add our starting point to the count and reset the start.
148 count += start;
149 start = 0;
150
San Mehat67bd2cd2010-01-12 12:18:49 -0800151 for (int i = 0; i < count; i++) {
152 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800153 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700154 buffer, start, i - start, StandardCharsets.UTF_8);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800155 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800156
San Mehat67bd2cd2010-01-12 12:18:49 -0800157 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800158 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
159 rawEvent);
160 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800161 // TODO: migrate to sending NativeDaemonEvent instances
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800162 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
163 event.getCode(), event.getRawEvent()));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800164 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800165 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800166 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800167 } catch (IllegalArgumentException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800168 log("Problem parsing message: " + rawEvent + " - " + e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800169 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800170
San Mehat67bd2cd2010-01-12 12:18:49 -0800171 start = i + 1;
172 }
173 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800174 if (start == 0) {
Elliott Hughesd396a442013-06-28 16:24:48 -0700175 final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800176 log("RCV incomplete <- {" + rawEvent + "}");
177 }
Kenny Root12da9d72010-09-02 22:18:14 -0700178
179 // We should end at the amount we read. If not, compact then
180 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700181 if (start != count) {
182 final int remaining = BUFFER_SIZE - start;
183 System.arraycopy(buffer, start, buffer, 0, remaining);
184 start = remaining;
185 } else {
186 start = 0;
187 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800188 }
189 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800190 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800191 throw ex;
192 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700193 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800194 if (mOutputStream != null) {
195 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800196 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800197 mOutputStream.close();
198 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800199 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800200 }
201 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800202 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800203 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800204
San Mehat4c27e0e2010-01-29 05:22:17 -0800205 try {
206 if (socket != null) {
207 socket.close();
208 }
209 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800210 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800211 }
212 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800213 }
214
San Mehat67bd2cd2010-01-12 12:18:49 -0800215 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700216 * Wrapper around argument that indicates it's sensitive and shouldn't be
217 * logged.
218 */
219 public static class SensitiveArg {
220 private final Object mArg;
221
222 public SensitiveArg(Object arg) {
223 mArg = arg;
224 }
225
226 @Override
227 public String toString() {
228 return String.valueOf(mArg);
229 }
230 }
231
232 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800233 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800234 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700235 @VisibleForTesting
236 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
237 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800238 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800239 throw new IllegalArgumentException("Unexpected command: " + cmd);
240 }
241 if (cmd.indexOf(' ') >= 0) {
242 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700243 }
244
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700245 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
246 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800247 for (Object arg : args) {
248 final String argString = String.valueOf(arg);
249 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800250 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700251 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800252
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700253 rawBuilder.append(' ');
254 logBuilder.append(' ');
255
256 appendEscaped(rawBuilder, argString);
257 if (arg instanceof SensitiveArg) {
258 logBuilder.append("[scrubbed]");
259 } else {
260 appendEscaped(logBuilder, argString);
261 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800262 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700263
264 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800265 }
San Mehatdeba6932010-01-20 15:14:31 -0800266
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700267 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800268 * Issue the given command to the native daemon and return a single expected
269 * response.
270 *
271 * @throws NativeDaemonConnectorException when problem communicating with
272 * native daemon, or if the response matches
273 * {@link NativeDaemonEvent#isClassClientError()} or
274 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800275 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800276 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
277 return execute(cmd.mCmd, cmd.mArguments.toArray());
278 }
279
280 /**
281 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800282 * response. Any arguments must be separated from base command so they can
283 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800284 *
285 * @throws NativeDaemonConnectorException when problem communicating with
286 * native daemon, or if the response matches
287 * {@link NativeDaemonEvent#isClassClientError()} or
288 * {@link NativeDaemonEvent#isClassServerError()}.
289 */
290 public NativeDaemonEvent execute(String cmd, Object... args)
291 throws NativeDaemonConnectorException {
292 final NativeDaemonEvent[] events = executeForList(cmd, args);
293 if (events.length != 1) {
294 throw new NativeDaemonConnectorException(
295 "Expected exactly one response, but received " + events.length);
296 }
297 return events[0];
298 }
299
300 /**
301 * Issue the given command to the native daemon and return any
302 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
303 * final terminal response.
304 *
305 * @throws NativeDaemonConnectorException when problem communicating with
306 * native daemon, or if the response matches
307 * {@link NativeDaemonEvent#isClassClientError()} or
308 * {@link NativeDaemonEvent#isClassServerError()}.
309 */
310 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
311 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
312 }
313
314 /**
315 * Issue the given command to the native daemon and return any
316 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800317 * final terminal response. Any arguments must be separated from base
318 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800319 *
320 * @throws NativeDaemonConnectorException when problem communicating with
321 * native daemon, or if the response matches
322 * {@link NativeDaemonEvent#isClassClientError()} or
323 * {@link NativeDaemonEvent#isClassServerError()}.
324 */
325 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800326 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800327 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800328 }
San Mehatdeba6932010-01-20 15:14:31 -0800329
Robert Greenwalt470007f2012-02-07 11:36:55 -0800330 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800331 * Issue the given command to the native daemon and return any {@linke
332 * NativeDaemonEvent@isClassContinue()} responses, including the final
333 * terminal response. Note that the timeout does not count time in deep
334 * sleep. Any arguments must be separated from base command so they can be
335 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800336 *
337 * @throws NativeDaemonConnectorException when problem communicating with
338 * native daemon, or if the response matches
339 * {@link NativeDaemonEvent#isClassClientError()} or
340 * {@link NativeDaemonEvent#isClassServerError()}.
341 */
342 public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800343 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700344 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700345
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700346 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700347
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700348 final StringBuilder rawBuilder = new StringBuilder();
349 final StringBuilder logBuilder = new StringBuilder();
350 final int sequenceNumber = mSequenceNumber.incrementAndGet();
351
352 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
353
354 final String rawCmd = rawBuilder.toString();
355 final String logCmd = logBuilder.toString();
356
Robert Greenwaltd1925982012-03-12 15:37:40 -0700357 log("SND -> {" + logCmd + "}");
358
Robert Greenwaltd1925982012-03-12 15:37:40 -0700359 synchronized (mDaemonLock) {
360 if (mOutputStream == null) {
361 throw new NativeDaemonConnectorException("missing output stream");
362 } else {
363 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700364 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700365 } catch (IOException e) {
366 throw new NativeDaemonConnectorException("problem sending command", e);
367 }
368 }
369 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800370
371 NativeDaemonEvent event = null;
372 do {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700373 event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800374 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700375 loge("timed-out waiting for response to " + logCmd);
376 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800377 }
Robert Greenwaltb5aff3f2012-05-15 17:26:57 -0700378 log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800379 events.add(event);
380 } while (event.isClassContinue());
381
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700382 final long endTime = SystemClock.elapsedRealtime();
383 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
384 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
385 }
386
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800387 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700388 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800389 }
390 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700391 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800392 }
393
394 return events.toArray(new NativeDaemonEvent[events.size()]);
395 }
396
397 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800398 * Append the given argument to {@link StringBuilder}, escaping as needed,
399 * and surrounding with quotes when it contains spaces.
400 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800401 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800402 static void appendEscaped(StringBuilder builder, String arg) {
403 final boolean hasSpaces = arg.indexOf(' ') >= 0;
404 if (hasSpaces) {
405 builder.append('"');
406 }
407
408 final int length = arg.length();
409 for (int i = 0; i < length; i++) {
410 final char c = arg.charAt(i);
411
412 if (c == '"') {
413 builder.append("\\\"");
414 } else if (c == '\\') {
415 builder.append("\\\\");
416 } else {
417 builder.append(c);
418 }
419 }
420
421 if (hasSpaces) {
422 builder.append('"');
423 }
424 }
425
426 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
427 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
428 super(command, event);
429 }
430
431 @Override
432 public IllegalArgumentException rethrowAsParcelableException() {
433 throw new IllegalArgumentException(getMessage(), this);
434 }
435 }
436
437 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
438 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
439 super(command, event);
440 }
San Mehatdeba6932010-01-20 15:14:31 -0800441 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700442
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800443 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800444 * Command builder that handles argument list building. Any arguments must
445 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800446 */
447 public static class Command {
448 private String mCmd;
449 private ArrayList<Object> mArguments = Lists.newArrayList();
450
451 public Command(String cmd, Object... args) {
452 mCmd = cmd;
453 for (Object arg : args) {
454 appendArg(arg);
455 }
456 }
457
458 public Command appendArg(Object arg) {
459 mArguments.add(arg);
460 return this;
461 }
462 }
463
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700464 /** {@inheritDoc} */
465 public void monitor() {
466 synchronized (mDaemonLock) { }
467 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800468
469 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
470 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800471 pw.println();
472 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800473 }
474
475 private void log(String logstring) {
476 if (LOGD) Slog.d(TAG, logstring);
477 mLocalLog.log(logstring);
478 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800479
480 private void loge(String logstring) {
481 Slog.e(TAG, logstring);
482 mLocalLog.log(logstring);
483 }
484
485 private static class ResponseQueue {
486
Robert Greenwaltef215992012-06-05 11:48:40 -0700487 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700488 public final int cmdNum;
489 public final String logCmd;
490
Robert Greenwaltef215992012-06-05 11:48:40 -0700491 public BlockingQueue<NativeDaemonEvent> responses =
492 new ArrayBlockingQueue<NativeDaemonEvent>(10);
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;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700510
511 public PendingCmd(int cmdNum, String logCmd) {
512 this.cmdNum = cmdNum;
513 this.logCmd = logCmd;
514 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800515 }
516
Robert Greenwaltef215992012-06-05 11:48:40 -0700517 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800518 private int mMaxCount;
519
520 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700521 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800522 mMaxCount = maxCount;
523 }
524
525 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700526 PendingCmd found = null;
527 synchronized (mPendingCmds) {
528 for (PendingCmd pendingCmd : mPendingCmds) {
529 if (pendingCmd.cmdNum == cmdNum) {
530 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800531 break;
532 }
533 }
534 if (found == null) {
535 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700536 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800537 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700538 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800539 " >= " + mMaxCount);
540 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700541 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800542 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700543 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700544 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800545 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700546 found = new PendingCmd(cmdNum, null);
547 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800548 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700549 found.availableResponseCount++;
550 // if a matching remove call has already retrieved this we can remove this
551 // instance from our list
552 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800553 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700554 try {
555 found.responses.put(response);
556 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800557 }
558
559 // note that the timeout does not count time in deep sleep. If you don't want
560 // the device to sleep, hold a wakelock
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700561 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700562 PendingCmd found = null;
563 synchronized (mPendingCmds) {
564 for (PendingCmd pendingCmd : mPendingCmds) {
565 if (pendingCmd.cmdNum == cmdNum) {
566 found = pendingCmd;
567 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800568 }
569 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700570 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700571 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700572 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800573 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700574 found.availableResponseCount--;
575 // if a matching add call has already retrieved this we can remove this
576 // instance from our list
577 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800578 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700579 NativeDaemonEvent result = null;
580 try {
581 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
582 } catch (InterruptedException e) {}
583 if (result == null) {
584 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
585 }
586 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800587 }
588
589 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
590 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700591 synchronized (mPendingCmds) {
592 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700593 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800594 }
595 }
596 }
597 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800598}