blob: 0d1e122afc2574c9d796af5fe201c331a7eb775d [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 {
Jeff Sharkey31c6e482011-11-18 17:09:01 -080051 private static final boolean LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080052
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
Robert Greenwalt470007f2012-02-07 11:36:55 -080059 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080060
Dianne Hackborn77b987f2014-02-26 16:20:52 -080061 private final PowerManager.WakeLock mWakeLock;
62
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070063 private final Looper mLooper;
64
San Mehat67bd2cd2010-01-12 12:18:49 -080065 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080066 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080067
Robert Greenwalt470007f2012-02-07 11:36:55 -080068 private AtomicInteger mSequenceNumber;
69
70 private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070071 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080072
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070073 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080074 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070075
Kenny Root961aa8c2010-03-22 18:02:45 -070076 private final int BUFFER_SIZE = 4096;
77
Jeff Sharkey31c6e482011-11-18 17:09:01 -080078 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Dianne Hackborn77b987f2014-02-26 16:20:52 -080079 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070080 this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
81 FgThread.get().getLooper());
82 }
83
84 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
85 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
86 Looper looper) {
San Mehat67bd2cd2010-01-12 12:18:49 -080087 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080088 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080089 mResponseQueue = new ResponseQueue(responseQueueSize);
Dianne Hackborn77b987f2014-02-26 16:20:52 -080090 mWakeLock = wl;
91 if (mWakeLock != null) {
92 mWakeLock.setReferenceCounted(true);
93 }
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070094 mLooper = looper;
Robert Greenwalt470007f2012-02-07 11:36:55 -080095 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -080096 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -080097 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -080098 }
99
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700100 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -0800101 public void run() {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -0700102 mCallbackHandler = new Handler(mLooper, this);
San Mehat67bd2cd2010-01-12 12:18:49 -0800103
104 while (true) {
105 try {
106 listenToSocket();
107 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800108 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800109 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -0800110 }
111 }
112 }
113
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700114 @Override
115 public boolean handleMessage(Message msg) {
116 String event = (String) msg.obj;
117 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700118 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800119 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700120 }
121 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800122 loge("Error handling '" + event + "': " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800123 } finally {
Dianne Hackborn4590e522014-03-24 13:36:46 -0700124 if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800125 mWakeLock.release();
126 }
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700127 }
128 return true;
129 }
130
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900131 private LocalSocketAddress determineSocketAddress() {
132 // If we're testing, set up a socket in a namespace that's accessible to test code.
133 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
134 // production devices, even if said native daemons ill-advisedly pick a socket name that
135 // starts with __test__, only allow this on debug builds.
136 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
137 return new LocalSocketAddress(mSocket);
138 } else {
139 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
140 }
141 }
142
San Mehat4c27e0e2010-01-29 05:22:17 -0800143 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700144 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800145
146 try {
147 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900148 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800149
150 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800151
152 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800153 synchronized (mDaemonLock) {
154 mOutputStream = socket.getOutputStream();
155 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800156
anga030bc882011-02-01 14:10:25 +0100157 mCallbacks.onDaemonConnected();
158
Kenny Root961aa8c2010-03-22 18:02:45 -0700159 byte[] buffer = new byte[BUFFER_SIZE];
160 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800161
162 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700163 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800164 if (count < 0) {
165 loge("got " + count + " reading with start = " + start);
166 break;
167 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800168
Kenny Root12da9d72010-09-02 22:18:14 -0700169 // Add our starting point to the count and reset the start.
170 count += start;
171 start = 0;
172
San Mehat67bd2cd2010-01-12 12:18:49 -0800173 for (int i = 0; i < count; i++) {
174 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800175 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700176 buffer, start, i - start, StandardCharsets.UTF_8);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800177 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800178
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800179 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800180 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800181 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
182 rawEvent);
183 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800184 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700185 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
186 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800187 mWakeLock.acquire();
188 releaseWl = true;
189 }
190 if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
191 event.getCode(), event.getRawEvent()))) {
192 releaseWl = false;
193 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800194 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800195 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800196 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800197 } catch (IllegalArgumentException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800198 log("Problem parsing message: " + rawEvent + " - " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800199 } finally {
200 if (releaseWl) {
201 mWakeLock.acquire();
202 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800203 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800204
San Mehat67bd2cd2010-01-12 12:18:49 -0800205 start = i + 1;
206 }
207 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800208 if (start == 0) {
Elliott Hughesd396a442013-06-28 16:24:48 -0700209 final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800210 log("RCV incomplete <- {" + rawEvent + "}");
211 }
Kenny Root12da9d72010-09-02 22:18:14 -0700212
213 // We should end at the amount we read. If not, compact then
214 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700215 if (start != count) {
216 final int remaining = BUFFER_SIZE - start;
217 System.arraycopy(buffer, start, buffer, 0, remaining);
218 start = remaining;
219 } else {
220 start = 0;
221 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800222 }
223 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800224 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800225 throw ex;
226 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700227 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800228 if (mOutputStream != null) {
229 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800230 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800231 mOutputStream.close();
232 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800233 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800234 }
235 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800236 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800237 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800238
San Mehat4c27e0e2010-01-29 05:22:17 -0800239 try {
240 if (socket != null) {
241 socket.close();
242 }
243 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800244 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800245 }
246 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800247 }
248
San Mehat67bd2cd2010-01-12 12:18:49 -0800249 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700250 * Wrapper around argument that indicates it's sensitive and shouldn't be
251 * logged.
252 */
253 public static class SensitiveArg {
254 private final Object mArg;
255
256 public SensitiveArg(Object arg) {
257 mArg = arg;
258 }
259
260 @Override
261 public String toString() {
262 return String.valueOf(mArg);
263 }
264 }
265
266 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800267 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800268 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700269 @VisibleForTesting
270 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
271 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800272 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800273 throw new IllegalArgumentException("Unexpected command: " + cmd);
274 }
275 if (cmd.indexOf(' ') >= 0) {
276 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700277 }
278
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700279 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
280 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800281 for (Object arg : args) {
282 final String argString = String.valueOf(arg);
283 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800284 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700285 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800286
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700287 rawBuilder.append(' ');
288 logBuilder.append(' ');
289
290 appendEscaped(rawBuilder, argString);
291 if (arg instanceof SensitiveArg) {
292 logBuilder.append("[scrubbed]");
293 } else {
294 appendEscaped(logBuilder, argString);
295 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800296 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700297
298 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800299 }
San Mehatdeba6932010-01-20 15:14:31 -0800300
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700301 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800302 * Issue the given command to the native daemon and return a single expected
303 * 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()}.
San Mehatdeba6932010-01-20 15:14:31 -0800309 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800310 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
311 return execute(cmd.mCmd, cmd.mArguments.toArray());
312 }
313
314 /**
315 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800316 * response. Any arguments must be separated from base command so they can
317 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800318 *
319 * @throws NativeDaemonConnectorException when problem communicating with
320 * native daemon, or if the response matches
321 * {@link NativeDaemonEvent#isClassClientError()} or
322 * {@link NativeDaemonEvent#isClassServerError()}.
323 */
324 public NativeDaemonEvent execute(String cmd, Object... args)
325 throws NativeDaemonConnectorException {
326 final NativeDaemonEvent[] events = executeForList(cmd, args);
327 if (events.length != 1) {
328 throw new NativeDaemonConnectorException(
329 "Expected exactly one response, but received " + events.length);
330 }
331 return events[0];
332 }
333
334 /**
335 * Issue the given command to the native daemon and return any
336 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
337 * final terminal response.
338 *
339 * @throws NativeDaemonConnectorException when problem communicating with
340 * native daemon, or if the response matches
341 * {@link NativeDaemonEvent#isClassClientError()} or
342 * {@link NativeDaemonEvent#isClassServerError()}.
343 */
344 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
345 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
346 }
347
348 /**
349 * Issue the given command to the native daemon and return any
350 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800351 * final terminal response. Any arguments must be separated from base
352 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800353 *
354 * @throws NativeDaemonConnectorException when problem communicating with
355 * native daemon, or if the response matches
356 * {@link NativeDaemonEvent#isClassClientError()} or
357 * {@link NativeDaemonEvent#isClassServerError()}.
358 */
359 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800360 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800361 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800362 }
San Mehatdeba6932010-01-20 15:14:31 -0800363
Robert Greenwalt470007f2012-02-07 11:36:55 -0800364 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800365 * Issue the given command to the native daemon and return any {@linke
366 * NativeDaemonEvent@isClassContinue()} responses, including the final
367 * terminal response. Note that the timeout does not count time in deep
368 * sleep. Any arguments must be separated from base command so they can be
369 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800370 *
371 * @throws NativeDaemonConnectorException when problem communicating with
372 * native daemon, or if the response matches
373 * {@link NativeDaemonEvent#isClassClientError()} or
374 * {@link NativeDaemonEvent#isClassServerError()}.
375 */
376 public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800377 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700378 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700379
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700380 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700381
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700382 final StringBuilder rawBuilder = new StringBuilder();
383 final StringBuilder logBuilder = new StringBuilder();
384 final int sequenceNumber = mSequenceNumber.incrementAndGet();
385
386 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
387
388 final String rawCmd = rawBuilder.toString();
389 final String logCmd = logBuilder.toString();
390
Robert Greenwaltd1925982012-03-12 15:37:40 -0700391 log("SND -> {" + logCmd + "}");
392
Robert Greenwaltd1925982012-03-12 15:37:40 -0700393 synchronized (mDaemonLock) {
394 if (mOutputStream == null) {
395 throw new NativeDaemonConnectorException("missing output stream");
396 } else {
397 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700398 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700399 } catch (IOException e) {
400 throw new NativeDaemonConnectorException("problem sending command", e);
401 }
402 }
403 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800404
405 NativeDaemonEvent event = null;
406 do {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700407 event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800408 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700409 loge("timed-out waiting for response to " + logCmd);
410 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800411 }
Robert Greenwaltb5aff3f2012-05-15 17:26:57 -0700412 log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800413 events.add(event);
414 } while (event.isClassContinue());
415
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700416 final long endTime = SystemClock.elapsedRealtime();
417 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
418 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
419 }
420
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800421 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700422 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800423 }
424 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700425 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800426 }
427
428 return events.toArray(new NativeDaemonEvent[events.size()]);
429 }
430
431 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800432 * Append the given argument to {@link StringBuilder}, escaping as needed,
433 * and surrounding with quotes when it contains spaces.
434 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800435 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800436 static void appendEscaped(StringBuilder builder, String arg) {
437 final boolean hasSpaces = arg.indexOf(' ') >= 0;
438 if (hasSpaces) {
439 builder.append('"');
440 }
441
442 final int length = arg.length();
443 for (int i = 0; i < length; i++) {
444 final char c = arg.charAt(i);
445
446 if (c == '"') {
447 builder.append("\\\"");
448 } else if (c == '\\') {
449 builder.append("\\\\");
450 } else {
451 builder.append(c);
452 }
453 }
454
455 if (hasSpaces) {
456 builder.append('"');
457 }
458 }
459
460 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
461 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
462 super(command, event);
463 }
464
465 @Override
466 public IllegalArgumentException rethrowAsParcelableException() {
467 throw new IllegalArgumentException(getMessage(), this);
468 }
469 }
470
471 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
472 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
473 super(command, event);
474 }
San Mehatdeba6932010-01-20 15:14:31 -0800475 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700476
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800477 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800478 * Command builder that handles argument list building. Any arguments must
479 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800480 */
481 public static class Command {
482 private String mCmd;
483 private ArrayList<Object> mArguments = Lists.newArrayList();
484
485 public Command(String cmd, Object... args) {
486 mCmd = cmd;
487 for (Object arg : args) {
488 appendArg(arg);
489 }
490 }
491
492 public Command appendArg(Object arg) {
493 mArguments.add(arg);
494 return this;
495 }
496 }
497
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700498 /** {@inheritDoc} */
499 public void monitor() {
500 synchronized (mDaemonLock) { }
501 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800502
503 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
504 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800505 pw.println();
506 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800507 }
508
509 private void log(String logstring) {
510 if (LOGD) Slog.d(TAG, logstring);
511 mLocalLog.log(logstring);
512 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800513
514 private void loge(String logstring) {
515 Slog.e(TAG, logstring);
516 mLocalLog.log(logstring);
517 }
518
519 private static class ResponseQueue {
520
Robert Greenwaltef215992012-06-05 11:48:40 -0700521 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700522 public final int cmdNum;
523 public final String logCmd;
524
Robert Greenwaltef215992012-06-05 11:48:40 -0700525 public BlockingQueue<NativeDaemonEvent> responses =
526 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700527
528 // The availableResponseCount member is used to track when we can remove this
529 // instance from the ResponseQueue.
530 // This is used under the protection of a sync of the mPendingCmds object.
531 // A positive value means we've had more writers retreive this object while
532 // a negative value means we've had more readers. When we've had an equal number
533 // (it goes to zero) we can remove this object from the mPendingCmds list.
534 // Note that we may have more responses for this command (and more readers
535 // coming), but that would result in a new PendingCmd instance being created
536 // and added with the same cmdNum.
537 // Also note that when this goes to zero it just means a parity of readers and
538 // writers have retrieved this object - not that they are done using it. The
539 // responses queue may well have more responses yet to be read or may get more
540 // responses added to it. But all those readers/writers have retreived and
541 // hold references to this instance already so it can be removed from
542 // mPendingCmds queue.
543 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700544
545 public PendingCmd(int cmdNum, String logCmd) {
546 this.cmdNum = cmdNum;
547 this.logCmd = logCmd;
548 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800549 }
550
Robert Greenwaltef215992012-06-05 11:48:40 -0700551 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800552 private int mMaxCount;
553
554 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700555 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800556 mMaxCount = maxCount;
557 }
558
559 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700560 PendingCmd found = null;
561 synchronized (mPendingCmds) {
562 for (PendingCmd pendingCmd : mPendingCmds) {
563 if (pendingCmd.cmdNum == cmdNum) {
564 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800565 break;
566 }
567 }
568 if (found == null) {
569 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700570 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800571 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700572 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800573 " >= " + mMaxCount);
574 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700575 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800576 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700577 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700578 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800579 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700580 found = new PendingCmd(cmdNum, null);
581 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800582 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700583 found.availableResponseCount++;
584 // if a matching remove call has already retrieved this we can remove this
585 // instance from our list
586 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800587 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700588 try {
589 found.responses.put(response);
590 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800591 }
592
593 // note that the timeout does not count time in deep sleep. If you don't want
594 // the device to sleep, hold a wakelock
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700595 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700596 PendingCmd found = null;
597 synchronized (mPendingCmds) {
598 for (PendingCmd pendingCmd : mPendingCmds) {
599 if (pendingCmd.cmdNum == cmdNum) {
600 found = pendingCmd;
601 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800602 }
603 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700604 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700605 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700606 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800607 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700608 found.availableResponseCount--;
609 // if a matching add call has already retrieved this we can remove this
610 // instance from our list
611 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800612 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700613 NativeDaemonEvent result = null;
614 try {
615 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
616 } catch (InterruptedException e) {}
617 if (result == null) {
618 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
619 }
620 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800621 }
622
623 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
624 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700625 synchronized (mPendingCmds) {
626 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700627 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800628 }
629 }
630 }
631 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800632}