blob: d2dfc7bbdc8201d622ddb85d22677e0de5ee7046 [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
Robert Greenwalt7f44ff82014-05-07 23:49:08 -070053 private final static boolean VDBG = false;
54
Jeff Sharkey31c6e482011-11-18 17:09:01 -080055 private final String TAG;
56
57 private String mSocket;
58 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080059 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080060
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
72 private static final int 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
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700102 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -0800103 public void run() {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -0700104 mCallbackHandler = new Handler(mLooper, this);
San Mehat67bd2cd2010-01-12 12:18:49 -0800105
106 while (true) {
107 try {
108 listenToSocket();
109 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800110 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800111 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -0800112 }
113 }
114 }
115
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700116 @Override
117 public boolean handleMessage(Message msg) {
118 String event = (String) msg.obj;
119 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700120 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800121 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700122 }
123 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800124 loge("Error handling '" + event + "': " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800125 } finally {
Dianne Hackborn4590e522014-03-24 13:36:46 -0700126 if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800127 mWakeLock.release();
128 }
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700129 }
130 return true;
131 }
132
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900133 private LocalSocketAddress determineSocketAddress() {
134 // If we're testing, set up a socket in a namespace that's accessible to test code.
135 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
136 // production devices, even if said native daemons ill-advisedly pick a socket name that
137 // starts with __test__, only allow this on debug builds.
138 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
139 return new LocalSocketAddress(mSocket);
140 } else {
141 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
142 }
143 }
144
San Mehat4c27e0e2010-01-29 05:22:17 -0800145 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700146 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800147
148 try {
149 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900150 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800151
152 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800153
154 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800155 synchronized (mDaemonLock) {
156 mOutputStream = socket.getOutputStream();
157 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800158
anga030bc882011-02-01 14:10:25 +0100159 mCallbacks.onDaemonConnected();
160
Kenny Root961aa8c2010-03-22 18:02:45 -0700161 byte[] buffer = new byte[BUFFER_SIZE];
162 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800163
164 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700165 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800166 if (count < 0) {
167 loge("got " + count + " reading with start = " + start);
168 break;
169 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800170
Kenny Root12da9d72010-09-02 22:18:14 -0700171 // Add our starting point to the count and reset the start.
172 count += start;
173 start = 0;
174
San Mehat67bd2cd2010-01-12 12:18:49 -0800175 for (int i = 0; i < count; i++) {
176 if (buffer[i] == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800177 // Note - do not log this raw message since it may contain
178 // sensitive data
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800179 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700180 buffer, start, i - start, StandardCharsets.UTF_8);
San Mehat67bd2cd2010-01-12 12:18:49 -0800181
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800182 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800183 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800184 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
185 rawEvent);
Paul Lawrencec38182f2014-11-11 12:23:22 -0800186
187 log("RCV <- {" + event + "}");
188
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800189 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800190 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700191 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
192 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800193 mWakeLock.acquire();
194 releaseWl = true;
195 }
196 if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
197 event.getCode(), event.getRawEvent()))) {
198 releaseWl = false;
199 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800200 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800201 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800202 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800203 } catch (IllegalArgumentException e) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800204 log("Problem parsing message " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800205 } finally {
206 if (releaseWl) {
207 mWakeLock.acquire();
208 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800209 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800210
San Mehat67bd2cd2010-01-12 12:18:49 -0800211 start = i + 1;
212 }
213 }
Paul Lawrencec38182f2014-11-11 12:23:22 -0800214
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800215 if (start == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800216 log("RCV incomplete");
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800217 }
Kenny Root12da9d72010-09-02 22:18:14 -0700218
219 // We should end at the amount we read. If not, compact then
220 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700221 if (start != count) {
222 final int remaining = BUFFER_SIZE - start;
223 System.arraycopy(buffer, start, buffer, 0, remaining);
224 start = remaining;
225 } else {
226 start = 0;
227 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800228 }
229 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800230 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800231 throw ex;
232 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700233 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800234 if (mOutputStream != null) {
235 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800236 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800237 mOutputStream.close();
238 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800239 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800240 }
241 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800242 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800243 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800244
San Mehat4c27e0e2010-01-29 05:22:17 -0800245 try {
246 if (socket != null) {
247 socket.close();
248 }
249 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800250 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800251 }
252 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800253 }
254
San Mehat67bd2cd2010-01-12 12:18:49 -0800255 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700256 * Wrapper around argument that indicates it's sensitive and shouldn't be
257 * logged.
258 */
259 public static class SensitiveArg {
260 private final Object mArg;
261
262 public SensitiveArg(Object arg) {
263 mArg = arg;
264 }
265
266 @Override
267 public String toString() {
268 return String.valueOf(mArg);
269 }
270 }
271
272 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800273 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800274 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700275 @VisibleForTesting
276 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
277 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800278 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800279 throw new IllegalArgumentException("Unexpected command: " + cmd);
280 }
281 if (cmd.indexOf(' ') >= 0) {
282 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700283 }
284
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700285 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
286 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800287 for (Object arg : args) {
288 final String argString = String.valueOf(arg);
289 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800290 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700291 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800292
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700293 rawBuilder.append(' ');
294 logBuilder.append(' ');
295
296 appendEscaped(rawBuilder, argString);
297 if (arg instanceof SensitiveArg) {
298 logBuilder.append("[scrubbed]");
299 } else {
300 appendEscaped(logBuilder, argString);
301 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800302 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700303
304 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800305 }
San Mehatdeba6932010-01-20 15:14:31 -0800306
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700307 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800308 * Issue the given command to the native daemon and return a single expected
309 * response.
310 *
311 * @throws NativeDaemonConnectorException when problem communicating with
312 * native daemon, or if the response matches
313 * {@link NativeDaemonEvent#isClassClientError()} or
314 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800315 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800316 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
317 return execute(cmd.mCmd, cmd.mArguments.toArray());
318 }
319
320 /**
321 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800322 * response. Any arguments must be separated from base command so they can
323 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800324 *
325 * @throws NativeDaemonConnectorException when problem communicating with
326 * native daemon, or if the response matches
327 * {@link NativeDaemonEvent#isClassClientError()} or
328 * {@link NativeDaemonEvent#isClassServerError()}.
329 */
330 public NativeDaemonEvent execute(String cmd, Object... args)
331 throws NativeDaemonConnectorException {
332 final NativeDaemonEvent[] events = executeForList(cmd, args);
333 if (events.length != 1) {
334 throw new NativeDaemonConnectorException(
335 "Expected exactly one response, but received " + events.length);
336 }
337 return events[0];
338 }
339
340 /**
341 * Issue the given command to the native daemon and return any
342 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
343 * final terminal response.
344 *
345 * @throws NativeDaemonConnectorException when problem communicating with
346 * native daemon, or if the response matches
347 * {@link NativeDaemonEvent#isClassClientError()} or
348 * {@link NativeDaemonEvent#isClassServerError()}.
349 */
350 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
351 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
352 }
353
354 /**
355 * Issue the given command to the native daemon and return any
356 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800357 * final terminal response. Any arguments must be separated from base
358 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800359 *
360 * @throws NativeDaemonConnectorException when problem communicating with
361 * native daemon, or if the response matches
362 * {@link NativeDaemonEvent#isClassClientError()} or
363 * {@link NativeDaemonEvent#isClassServerError()}.
364 */
365 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800366 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800367 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800368 }
San Mehatdeba6932010-01-20 15:14:31 -0800369
Robert Greenwalt470007f2012-02-07 11:36:55 -0800370 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800371 * Issue the given command to the native daemon and return any {@linke
372 * NativeDaemonEvent@isClassContinue()} responses, including the final
373 * terminal response. Note that the timeout does not count time in deep
374 * sleep. Any arguments must be separated from base command so they can be
375 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800376 *
377 * @throws NativeDaemonConnectorException when problem communicating with
378 * native daemon, or if the response matches
379 * {@link NativeDaemonEvent#isClassClientError()} or
380 * {@link NativeDaemonEvent#isClassServerError()}.
381 */
382 public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800383 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700384 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700385
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700386 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700387
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700388 final StringBuilder rawBuilder = new StringBuilder();
389 final StringBuilder logBuilder = new StringBuilder();
390 final int sequenceNumber = mSequenceNumber.incrementAndGet();
391
392 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
393
394 final String rawCmd = rawBuilder.toString();
395 final String logCmd = logBuilder.toString();
396
Robert Greenwaltd1925982012-03-12 15:37:40 -0700397 log("SND -> {" + logCmd + "}");
398
Robert Greenwaltd1925982012-03-12 15:37:40 -0700399 synchronized (mDaemonLock) {
400 if (mOutputStream == null) {
401 throw new NativeDaemonConnectorException("missing output stream");
402 } else {
403 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700404 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700405 } catch (IOException e) {
406 throw new NativeDaemonConnectorException("problem sending command", e);
407 }
408 }
409 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800410
411 NativeDaemonEvent event = null;
412 do {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700413 event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800414 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700415 loge("timed-out waiting for response to " + logCmd);
416 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800417 }
Robert Greenwalt7f44ff82014-05-07 23:49:08 -0700418 if (VDBG) log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800419 events.add(event);
420 } while (event.isClassContinue());
421
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700422 final long endTime = SystemClock.elapsedRealtime();
423 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
424 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
425 }
426
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800427 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700428 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800429 }
430 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700431 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800432 }
433
434 return events.toArray(new NativeDaemonEvent[events.size()]);
435 }
436
437 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800438 * Append the given argument to {@link StringBuilder}, escaping as needed,
439 * and surrounding with quotes when it contains spaces.
440 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800441 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800442 static void appendEscaped(StringBuilder builder, String arg) {
443 final boolean hasSpaces = arg.indexOf(' ') >= 0;
444 if (hasSpaces) {
445 builder.append('"');
446 }
447
448 final int length = arg.length();
449 for (int i = 0; i < length; i++) {
450 final char c = arg.charAt(i);
451
452 if (c == '"') {
453 builder.append("\\\"");
454 } else if (c == '\\') {
455 builder.append("\\\\");
456 } else {
457 builder.append(c);
458 }
459 }
460
461 if (hasSpaces) {
462 builder.append('"');
463 }
464 }
465
466 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
467 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
468 super(command, event);
469 }
470
471 @Override
472 public IllegalArgumentException rethrowAsParcelableException() {
473 throw new IllegalArgumentException(getMessage(), this);
474 }
475 }
476
477 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
478 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
479 super(command, event);
480 }
San Mehatdeba6932010-01-20 15:14:31 -0800481 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700482
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800483 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800484 * Command builder that handles argument list building. Any arguments must
485 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800486 */
487 public static class Command {
488 private String mCmd;
489 private ArrayList<Object> mArguments = Lists.newArrayList();
490
491 public Command(String cmd, Object... args) {
492 mCmd = cmd;
493 for (Object arg : args) {
494 appendArg(arg);
495 }
496 }
497
498 public Command appendArg(Object arg) {
499 mArguments.add(arg);
500 return this;
501 }
502 }
503
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700504 /** {@inheritDoc} */
505 public void monitor() {
506 synchronized (mDaemonLock) { }
507 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800508
509 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
510 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800511 pw.println();
512 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800513 }
514
515 private void log(String logstring) {
516 if (LOGD) Slog.d(TAG, logstring);
517 mLocalLog.log(logstring);
518 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800519
520 private void loge(String logstring) {
521 Slog.e(TAG, logstring);
522 mLocalLog.log(logstring);
523 }
524
525 private static class ResponseQueue {
526
Robert Greenwaltef215992012-06-05 11:48:40 -0700527 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700528 public final int cmdNum;
529 public final String logCmd;
530
Robert Greenwaltef215992012-06-05 11:48:40 -0700531 public BlockingQueue<NativeDaemonEvent> responses =
532 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700533
534 // The availableResponseCount member is used to track when we can remove this
535 // instance from the ResponseQueue.
536 // This is used under the protection of a sync of the mPendingCmds object.
537 // A positive value means we've had more writers retreive this object while
538 // a negative value means we've had more readers. When we've had an equal number
539 // (it goes to zero) we can remove this object from the mPendingCmds list.
540 // Note that we may have more responses for this command (and more readers
541 // coming), but that would result in a new PendingCmd instance being created
542 // and added with the same cmdNum.
543 // Also note that when this goes to zero it just means a parity of readers and
544 // writers have retrieved this object - not that they are done using it. The
545 // responses queue may well have more responses yet to be read or may get more
546 // responses added to it. But all those readers/writers have retreived and
547 // hold references to this instance already so it can be removed from
548 // mPendingCmds queue.
549 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700550
551 public PendingCmd(int cmdNum, String logCmd) {
552 this.cmdNum = cmdNum;
553 this.logCmd = logCmd;
554 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800555 }
556
Robert Greenwaltef215992012-06-05 11:48:40 -0700557 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800558 private int mMaxCount;
559
560 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700561 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800562 mMaxCount = maxCount;
563 }
564
565 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700566 PendingCmd found = null;
567 synchronized (mPendingCmds) {
568 for (PendingCmd pendingCmd : mPendingCmds) {
569 if (pendingCmd.cmdNum == cmdNum) {
570 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800571 break;
572 }
573 }
574 if (found == null) {
575 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700576 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800577 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700578 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800579 " >= " + mMaxCount);
580 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700581 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800582 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700583 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700584 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800585 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700586 found = new PendingCmd(cmdNum, null);
587 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800588 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700589 found.availableResponseCount++;
590 // if a matching remove call has already retrieved this we can remove this
591 // instance from our list
592 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800593 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700594 try {
595 found.responses.put(response);
596 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800597 }
598
599 // note that the timeout does not count time in deep sleep. If you don't want
600 // the device to sleep, hold a wakelock
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700601 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700602 PendingCmd found = null;
603 synchronized (mPendingCmds) {
604 for (PendingCmd pendingCmd : mPendingCmds) {
605 if (pendingCmd.cmdNum == cmdNum) {
606 found = pendingCmd;
607 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800608 }
609 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700610 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700611 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700612 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800613 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700614 found.availableResponseCount--;
615 // if a matching add call has already retrieved this we can remove this
616 // instance from our list
617 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800618 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700619 NativeDaemonEvent result = null;
620 try {
621 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
622 } catch (InterruptedException e) {}
623 if (result == null) {
624 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
625 }
626 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800627 }
628
629 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
630 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700631 synchronized (mPendingCmds) {
632 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700633 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800634 }
635 }
636 }
637 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800638}