blob: 519a2a3f4f887cd57dade4650355b8f31d5574e5 [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;
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070023import android.os.Looper;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070024import android.os.Message;
Dianne Hackborn77b987f2014-02-26 16:20:52 -080025import android.os.PowerManager;
San Mehat67bd2cd2010-01-12 12:18:49 -080026import android.os.SystemClock;
Robert Greenwalt470fd722012-01-18 12:51:15 -080027import android.util.LocalLog;
Joe Onorato8a9b2202010-02-26 18:56:32 -080028import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080029
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080030import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080031import com.google.android.collect.Lists;
32
Robert Greenwalt470fd722012-01-18 12:51:15 -080033import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080034import java.io.IOException;
35import java.io.InputStream;
36import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080037import java.io.PrintWriter;
Elliott Hughesd396a442013-06-28 16:24:48 -070038import java.nio.charset.StandardCharsets;
San Mehat67bd2cd2010-01-12 12:18:49 -080039import java.util.ArrayList;
Robert Greenwalt470007f2012-02-07 11:36:55 -080040import java.util.concurrent.atomic.AtomicInteger;
Robert Greenwaltef215992012-06-05 11:48:40 -070041import java.util.concurrent.ArrayBlockingQueue;
42import java.util.concurrent.BlockingQueue;
43import java.util.concurrent.TimeUnit;
Robert Greenwalt470007f2012-02-07 11:36:55 -080044import java.util.LinkedList;
San Mehat67bd2cd2010-01-12 12:18:49 -080045
46/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080047 * Generic connector class for interfacing with a native daemon which uses the
48 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080049 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070050final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Robert Greenwalt7f44ff82014-05-07 23:49:08 -070051 private final static boolean VDBG = false;
52
Jeff Sharkey31c6e482011-11-18 17:09:01 -080053 private final String TAG;
54
55 private String mSocket;
56 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080057 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080058
Jeff Sharkey48877892015-03-18 11:27:19 -070059 private volatile boolean mDebug = false;
60
Robert Greenwalt470007f2012-02-07 11:36:55 -080061 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080062
Dianne Hackborn77b987f2014-02-26 16:20:52 -080063 private final PowerManager.WakeLock mWakeLock;
64
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070065 private final Looper mLooper;
66
San Mehat67bd2cd2010-01-12 12:18:49 -080067 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080068 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080069
Robert Greenwalt470007f2012-02-07 11:36:55 -080070 private AtomicInteger mSequenceNumber;
71
Jeff Sharkey14cbe522015-07-08 14:06:37 -070072 private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070073 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080074
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070075 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080076 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070077
Kenny Root961aa8c2010-03-22 18:02:45 -070078 private final int BUFFER_SIZE = 4096;
79
Jeff Sharkey31c6e482011-11-18 17:09:01 -080080 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Dianne Hackborn77b987f2014-02-26 16:20:52 -080081 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070082 this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
83 FgThread.get().getLooper());
84 }
85
86 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
87 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
88 Looper looper) {
San Mehat67bd2cd2010-01-12 12:18:49 -080089 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080090 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080091 mResponseQueue = new ResponseQueue(responseQueueSize);
Dianne Hackborn77b987f2014-02-26 16:20:52 -080092 mWakeLock = wl;
93 if (mWakeLock != null) {
94 mWakeLock.setReferenceCounted(true);
95 }
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070096 mLooper = looper;
Robert Greenwalt470007f2012-02-07 11:36:55 -080097 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -080098 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -080099 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -0800100 }
101
Jeff Sharkey48877892015-03-18 11:27:19 -0700102 /**
103 * Enable Set debugging mode, which causes messages to also be written to both
104 * {@link Slog} in addition to internal log.
105 */
106 public void setDebug(boolean debug) {
107 mDebug = debug;
108 }
109
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700110 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -0800111 public void run() {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -0700112 mCallbackHandler = new Handler(mLooper, this);
San Mehat67bd2cd2010-01-12 12:18:49 -0800113
114 while (true) {
115 try {
116 listenToSocket();
117 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800118 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800119 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -0800120 }
121 }
122 }
123
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700124 @Override
125 public boolean handleMessage(Message msg) {
126 String event = (String) msg.obj;
127 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700128 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800129 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700130 }
131 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800132 loge("Error handling '" + event + "': " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800133 } finally {
Dianne Hackborn4590e522014-03-24 13:36:46 -0700134 if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800135 mWakeLock.release();
136 }
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700137 }
138 return true;
139 }
140
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900141 private LocalSocketAddress determineSocketAddress() {
142 // If we're testing, set up a socket in a namespace that's accessible to test code.
143 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
144 // production devices, even if said native daemons ill-advisedly pick a socket name that
145 // starts with __test__, only allow this on debug builds.
146 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
147 return new LocalSocketAddress(mSocket);
148 } else {
149 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
150 }
151 }
152
San Mehat4c27e0e2010-01-29 05:22:17 -0800153 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700154 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800155
156 try {
157 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900158 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800159
160 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800161
162 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800163 synchronized (mDaemonLock) {
164 mOutputStream = socket.getOutputStream();
165 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800166
anga030bc882011-02-01 14:10:25 +0100167 mCallbacks.onDaemonConnected();
168
Kenny Root961aa8c2010-03-22 18:02:45 -0700169 byte[] buffer = new byte[BUFFER_SIZE];
170 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800171
172 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700173 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800174 if (count < 0) {
175 loge("got " + count + " reading with start = " + start);
176 break;
177 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800178
Kenny Root12da9d72010-09-02 22:18:14 -0700179 // Add our starting point to the count and reset the start.
180 count += start;
181 start = 0;
182
San Mehat67bd2cd2010-01-12 12:18:49 -0800183 for (int i = 0; i < count; i++) {
184 if (buffer[i] == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800185 // Note - do not log this raw message since it may contain
186 // sensitive data
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800187 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700188 buffer, start, i - start, StandardCharsets.UTF_8);
San Mehat67bd2cd2010-01-12 12:18:49 -0800189
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800190 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800191 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800192 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
193 rawEvent);
Paul Lawrencec38182f2014-11-11 12:23:22 -0800194
195 log("RCV <- {" + event + "}");
196
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800197 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800198 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700199 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
200 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800201 mWakeLock.acquire();
202 releaseWl = true;
203 }
204 if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
205 event.getCode(), event.getRawEvent()))) {
206 releaseWl = false;
207 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800208 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800209 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800210 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800211 } catch (IllegalArgumentException e) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800212 log("Problem parsing message " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800213 } finally {
214 if (releaseWl) {
215 mWakeLock.acquire();
216 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800217 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800218
San Mehat67bd2cd2010-01-12 12:18:49 -0800219 start = i + 1;
220 }
221 }
Paul Lawrencec38182f2014-11-11 12:23:22 -0800222
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800223 if (start == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800224 log("RCV incomplete");
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800225 }
Kenny Root12da9d72010-09-02 22:18:14 -0700226
227 // We should end at the amount we read. If not, compact then
228 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700229 if (start != count) {
230 final int remaining = BUFFER_SIZE - start;
231 System.arraycopy(buffer, start, buffer, 0, remaining);
232 start = remaining;
233 } else {
234 start = 0;
235 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800236 }
237 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800238 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800239 throw ex;
240 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700241 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800242 if (mOutputStream != null) {
243 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800244 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800245 mOutputStream.close();
246 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800247 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800248 }
249 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800250 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800251 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800252
San Mehat4c27e0e2010-01-29 05:22:17 -0800253 try {
254 if (socket != null) {
255 socket.close();
256 }
257 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800258 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800259 }
260 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800261 }
262
San Mehat67bd2cd2010-01-12 12:18:49 -0800263 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700264 * Wrapper around argument that indicates it's sensitive and shouldn't be
265 * logged.
266 */
267 public static class SensitiveArg {
268 private final Object mArg;
269
270 public SensitiveArg(Object arg) {
271 mArg = arg;
272 }
273
274 @Override
275 public String toString() {
276 return String.valueOf(mArg);
277 }
278 }
279
280 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800281 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800282 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700283 @VisibleForTesting
284 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
285 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800286 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800287 throw new IllegalArgumentException("Unexpected command: " + cmd);
288 }
289 if (cmd.indexOf(' ') >= 0) {
290 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700291 }
292
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700293 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
294 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800295 for (Object arg : args) {
296 final String argString = String.valueOf(arg);
297 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800298 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700299 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800300
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700301 rawBuilder.append(' ');
302 logBuilder.append(' ');
303
304 appendEscaped(rawBuilder, argString);
305 if (arg instanceof SensitiveArg) {
306 logBuilder.append("[scrubbed]");
307 } else {
308 appendEscaped(logBuilder, argString);
309 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800310 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700311
312 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800313 }
San Mehatdeba6932010-01-20 15:14:31 -0800314
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700315 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800316 * Issue the given command to the native daemon and return a single expected
317 * response.
318 *
319 * @throws NativeDaemonConnectorException when problem communicating with
320 * native daemon, or if the response matches
321 * {@link NativeDaemonEvent#isClassClientError()} or
322 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800323 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800324 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
325 return execute(cmd.mCmd, cmd.mArguments.toArray());
326 }
327
328 /**
329 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800330 * response. Any arguments must be separated from base command so they can
331 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800332 *
333 * @throws NativeDaemonConnectorException when problem communicating with
334 * native daemon, or if the response matches
335 * {@link NativeDaemonEvent#isClassClientError()} or
336 * {@link NativeDaemonEvent#isClassServerError()}.
337 */
338 public NativeDaemonEvent execute(String cmd, Object... args)
339 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700340 return execute(DEFAULT_TIMEOUT, cmd, args);
341 }
342
343 public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
344 throws NativeDaemonConnectorException {
345 final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800346 if (events.length != 1) {
347 throw new NativeDaemonConnectorException(
348 "Expected exactly one response, but received " + events.length);
349 }
350 return events[0];
351 }
352
353 /**
354 * Issue the given command to the native daemon and return any
355 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
356 * final terminal response.
357 *
358 * @throws NativeDaemonConnectorException when problem communicating with
359 * native daemon, or if the response matches
360 * {@link NativeDaemonEvent#isClassClientError()} or
361 * {@link NativeDaemonEvent#isClassServerError()}.
362 */
363 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
364 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
365 }
366
367 /**
368 * Issue the given command to the native daemon and return any
369 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800370 * final terminal response. Any arguments must be separated from base
371 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800372 *
373 * @throws NativeDaemonConnectorException when problem communicating with
374 * native daemon, or if the response matches
375 * {@link NativeDaemonEvent#isClassClientError()} or
376 * {@link NativeDaemonEvent#isClassServerError()}.
377 */
378 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800379 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700380 return executeForList(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800381 }
San Mehatdeba6932010-01-20 15:14:31 -0800382
Robert Greenwalt470007f2012-02-07 11:36:55 -0800383 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800384 * Issue the given command to the native daemon and return any {@linke
385 * NativeDaemonEvent@isClassContinue()} responses, including the final
386 * terminal response. Note that the timeout does not count time in deep
387 * sleep. Any arguments must be separated from base command so they can be
388 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800389 *
390 * @throws NativeDaemonConnectorException when problem communicating with
391 * native daemon, or if the response matches
392 * {@link NativeDaemonEvent#isClassClientError()} or
393 * {@link NativeDaemonEvent#isClassServerError()}.
394 */
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700395 public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800396 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700397 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700398
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700399 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700400
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700401 final StringBuilder rawBuilder = new StringBuilder();
402 final StringBuilder logBuilder = new StringBuilder();
403 final int sequenceNumber = mSequenceNumber.incrementAndGet();
404
405 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
406
407 final String rawCmd = rawBuilder.toString();
408 final String logCmd = logBuilder.toString();
409
Robert Greenwaltd1925982012-03-12 15:37:40 -0700410 log("SND -> {" + logCmd + "}");
411
Robert Greenwaltd1925982012-03-12 15:37:40 -0700412 synchronized (mDaemonLock) {
413 if (mOutputStream == null) {
414 throw new NativeDaemonConnectorException("missing output stream");
415 } else {
416 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700417 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700418 } catch (IOException e) {
419 throw new NativeDaemonConnectorException("problem sending command", e);
420 }
421 }
422 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800423
424 NativeDaemonEvent event = null;
425 do {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700426 event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800427 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700428 loge("timed-out waiting for response to " + logCmd);
Todd Kennedy8101ee62015-06-23 13:35:28 -0700429 throw new NativeDaemonTimeoutException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800430 }
Robert Greenwalt7f44ff82014-05-07 23:49:08 -0700431 if (VDBG) log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800432 events.add(event);
433 } while (event.isClassContinue());
434
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700435 final long endTime = SystemClock.elapsedRealtime();
436 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
437 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
438 }
439
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800440 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700441 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800442 }
443 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700444 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800445 }
446
447 return events.toArray(new NativeDaemonEvent[events.size()]);
448 }
449
450 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800451 * Append the given argument to {@link StringBuilder}, escaping as needed,
452 * and surrounding with quotes when it contains spaces.
453 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800454 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800455 static void appendEscaped(StringBuilder builder, String arg) {
456 final boolean hasSpaces = arg.indexOf(' ') >= 0;
457 if (hasSpaces) {
458 builder.append('"');
459 }
460
461 final int length = arg.length();
462 for (int i = 0; i < length; i++) {
463 final char c = arg.charAt(i);
464
465 if (c == '"') {
466 builder.append("\\\"");
467 } else if (c == '\\') {
468 builder.append("\\\\");
469 } else {
470 builder.append(c);
471 }
472 }
473
474 if (hasSpaces) {
475 builder.append('"');
476 }
477 }
478
479 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
480 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
481 super(command, event);
482 }
483
484 @Override
485 public IllegalArgumentException rethrowAsParcelableException() {
486 throw new IllegalArgumentException(getMessage(), this);
487 }
488 }
489
490 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
491 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
492 super(command, event);
493 }
San Mehatdeba6932010-01-20 15:14:31 -0800494 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700495
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800496 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800497 * Command builder that handles argument list building. Any arguments must
498 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800499 */
500 public static class Command {
501 private String mCmd;
502 private ArrayList<Object> mArguments = Lists.newArrayList();
503
504 public Command(String cmd, Object... args) {
505 mCmd = cmd;
506 for (Object arg : args) {
507 appendArg(arg);
508 }
509 }
510
511 public Command appendArg(Object arg) {
512 mArguments.add(arg);
513 return this;
514 }
515 }
516
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700517 /** {@inheritDoc} */
518 public void monitor() {
519 synchronized (mDaemonLock) { }
520 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800521
522 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
523 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800524 pw.println();
525 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800526 }
527
528 private void log(String logstring) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700529 if (mDebug) Slog.d(TAG, logstring);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800530 mLocalLog.log(logstring);
531 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800532
533 private void loge(String logstring) {
534 Slog.e(TAG, logstring);
535 mLocalLog.log(logstring);
536 }
537
538 private static class ResponseQueue {
539
Robert Greenwaltef215992012-06-05 11:48:40 -0700540 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700541 public final int cmdNum;
542 public final String logCmd;
543
Robert Greenwaltef215992012-06-05 11:48:40 -0700544 public BlockingQueue<NativeDaemonEvent> responses =
545 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700546
547 // The availableResponseCount member is used to track when we can remove this
548 // instance from the ResponseQueue.
549 // This is used under the protection of a sync of the mPendingCmds object.
550 // A positive value means we've had more writers retreive this object while
551 // a negative value means we've had more readers. When we've had an equal number
552 // (it goes to zero) we can remove this object from the mPendingCmds list.
553 // Note that we may have more responses for this command (and more readers
554 // coming), but that would result in a new PendingCmd instance being created
555 // and added with the same cmdNum.
556 // Also note that when this goes to zero it just means a parity of readers and
557 // writers have retrieved this object - not that they are done using it. The
558 // responses queue may well have more responses yet to be read or may get more
559 // responses added to it. But all those readers/writers have retreived and
560 // hold references to this instance already so it can be removed from
561 // mPendingCmds queue.
562 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700563
564 public PendingCmd(int cmdNum, String logCmd) {
565 this.cmdNum = cmdNum;
566 this.logCmd = logCmd;
567 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800568 }
569
Robert Greenwaltef215992012-06-05 11:48:40 -0700570 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800571 private int mMaxCount;
572
573 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700574 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800575 mMaxCount = maxCount;
576 }
577
578 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700579 PendingCmd found = null;
580 synchronized (mPendingCmds) {
581 for (PendingCmd pendingCmd : mPendingCmds) {
582 if (pendingCmd.cmdNum == cmdNum) {
583 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800584 break;
585 }
586 }
587 if (found == null) {
588 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700589 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800590 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700591 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800592 " >= " + mMaxCount);
593 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700594 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800595 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700596 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700597 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800598 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700599 found = new PendingCmd(cmdNum, null);
600 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800601 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700602 found.availableResponseCount++;
603 // if a matching remove call has already retrieved this we can remove this
604 // instance from our list
605 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800606 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700607 try {
608 found.responses.put(response);
609 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800610 }
611
612 // note that the timeout does not count time in deep sleep. If you don't want
613 // the device to sleep, hold a wakelock
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700614 public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700615 PendingCmd found = null;
616 synchronized (mPendingCmds) {
617 for (PendingCmd pendingCmd : mPendingCmds) {
618 if (pendingCmd.cmdNum == cmdNum) {
619 found = pendingCmd;
620 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800621 }
622 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700623 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700624 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700625 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800626 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700627 found.availableResponseCount--;
628 // if a matching add call has already retrieved this we can remove this
629 // instance from our list
630 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800631 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700632 NativeDaemonEvent result = null;
633 try {
634 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
635 } catch (InterruptedException e) {}
636 if (result == null) {
637 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
638 }
639 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800640 }
641
642 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
643 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700644 synchronized (mPendingCmds) {
645 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700646 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800647 }
648 }
649 }
650 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800651}