blob: 96f9ab061c106dbfc1add5cbec4a1c6c4fdea570 [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) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800177 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700178 buffer, start, i - start, StandardCharsets.UTF_8);
Paul Lawrenced66f3e52014-11-11 17:28:28 +0000179 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800180
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800181 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800182 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800183 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
184 rawEvent);
185 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800186 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700187 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
188 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800189 mWakeLock.acquire();
190 releaseWl = true;
191 }
192 if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
193 event.getCode(), event.getRawEvent()))) {
194 releaseWl = false;
195 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800196 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800197 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800198 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800199 } catch (IllegalArgumentException e) {
Paul Lawrenced66f3e52014-11-11 17:28:28 +0000200 log("Problem parsing message: " + rawEvent + " - " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800201 } finally {
202 if (releaseWl) {
203 mWakeLock.acquire();
204 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800205 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800206
San Mehat67bd2cd2010-01-12 12:18:49 -0800207 start = i + 1;
208 }
209 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800210 if (start == 0) {
Elliott Hughesd396a442013-06-28 16:24:48 -0700211 final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
Paul Lawrenced66f3e52014-11-11 17:28:28 +0000212 log("RCV incomplete <- {" + rawEvent + "}");
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800213 }
Kenny Root12da9d72010-09-02 22:18:14 -0700214
215 // We should end at the amount we read. If not, compact then
216 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700217 if (start != count) {
218 final int remaining = BUFFER_SIZE - start;
219 System.arraycopy(buffer, start, buffer, 0, remaining);
220 start = remaining;
221 } else {
222 start = 0;
223 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800224 }
225 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800226 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800227 throw ex;
228 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700229 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800230 if (mOutputStream != null) {
231 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800232 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800233 mOutputStream.close();
234 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800235 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800236 }
237 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800238 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800239 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800240
San Mehat4c27e0e2010-01-29 05:22:17 -0800241 try {
242 if (socket != null) {
243 socket.close();
244 }
245 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800246 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800247 }
248 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800249 }
250
San Mehat67bd2cd2010-01-12 12:18:49 -0800251 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700252 * Wrapper around argument that indicates it's sensitive and shouldn't be
253 * logged.
254 */
255 public static class SensitiveArg {
256 private final Object mArg;
257
258 public SensitiveArg(Object arg) {
259 mArg = arg;
260 }
261
262 @Override
263 public String toString() {
264 return String.valueOf(mArg);
265 }
266 }
267
268 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800269 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800270 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700271 @VisibleForTesting
272 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
273 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800274 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800275 throw new IllegalArgumentException("Unexpected command: " + cmd);
276 }
277 if (cmd.indexOf(' ') >= 0) {
278 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700279 }
280
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700281 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
282 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800283 for (Object arg : args) {
284 final String argString = String.valueOf(arg);
285 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800286 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700287 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800288
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700289 rawBuilder.append(' ');
290 logBuilder.append(' ');
291
292 appendEscaped(rawBuilder, argString);
293 if (arg instanceof SensitiveArg) {
294 logBuilder.append("[scrubbed]");
295 } else {
296 appendEscaped(logBuilder, argString);
297 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800298 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700299
300 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800301 }
San Mehatdeba6932010-01-20 15:14:31 -0800302
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700303 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800304 * Issue the given command to the native daemon and return a single expected
305 * response.
306 *
307 * @throws NativeDaemonConnectorException when problem communicating with
308 * native daemon, or if the response matches
309 * {@link NativeDaemonEvent#isClassClientError()} or
310 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800311 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800312 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
313 return execute(cmd.mCmd, cmd.mArguments.toArray());
314 }
315
316 /**
317 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800318 * response. Any arguments must be separated from base command so they can
319 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800320 *
321 * @throws NativeDaemonConnectorException when problem communicating with
322 * native daemon, or if the response matches
323 * {@link NativeDaemonEvent#isClassClientError()} or
324 * {@link NativeDaemonEvent#isClassServerError()}.
325 */
326 public NativeDaemonEvent execute(String cmd, Object... args)
327 throws NativeDaemonConnectorException {
328 final NativeDaemonEvent[] events = executeForList(cmd, args);
329 if (events.length != 1) {
330 throw new NativeDaemonConnectorException(
331 "Expected exactly one response, but received " + events.length);
332 }
333 return events[0];
334 }
335
336 /**
337 * Issue the given command to the native daemon and return any
338 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
339 * final terminal response.
340 *
341 * @throws NativeDaemonConnectorException when problem communicating with
342 * native daemon, or if the response matches
343 * {@link NativeDaemonEvent#isClassClientError()} or
344 * {@link NativeDaemonEvent#isClassServerError()}.
345 */
346 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
347 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
348 }
349
350 /**
351 * Issue the given command to the native daemon and return any
352 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800353 * final terminal response. Any arguments must be separated from base
354 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800355 *
356 * @throws NativeDaemonConnectorException when problem communicating with
357 * native daemon, or if the response matches
358 * {@link NativeDaemonEvent#isClassClientError()} or
359 * {@link NativeDaemonEvent#isClassServerError()}.
360 */
361 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800362 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800363 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800364 }
San Mehatdeba6932010-01-20 15:14:31 -0800365
Robert Greenwalt470007f2012-02-07 11:36:55 -0800366 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800367 * Issue the given command to the native daemon and return any {@linke
368 * NativeDaemonEvent@isClassContinue()} responses, including the final
369 * terminal response. Note that the timeout does not count time in deep
370 * sleep. Any arguments must be separated from base command so they can be
371 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -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[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800379 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700380 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700381
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700382 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700383
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700384 final StringBuilder rawBuilder = new StringBuilder();
385 final StringBuilder logBuilder = new StringBuilder();
386 final int sequenceNumber = mSequenceNumber.incrementAndGet();
387
388 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
389
390 final String rawCmd = rawBuilder.toString();
391 final String logCmd = logBuilder.toString();
392
Robert Greenwaltd1925982012-03-12 15:37:40 -0700393 log("SND -> {" + logCmd + "}");
394
Robert Greenwaltd1925982012-03-12 15:37:40 -0700395 synchronized (mDaemonLock) {
396 if (mOutputStream == null) {
397 throw new NativeDaemonConnectorException("missing output stream");
398 } else {
399 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700400 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700401 } catch (IOException e) {
402 throw new NativeDaemonConnectorException("problem sending command", e);
403 }
404 }
405 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800406
407 NativeDaemonEvent event = null;
408 do {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700409 event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800410 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700411 loge("timed-out waiting for response to " + logCmd);
412 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800413 }
Robert Greenwalt7f44ff82014-05-07 23:49:08 -0700414 if (VDBG) log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800415 events.add(event);
416 } while (event.isClassContinue());
417
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700418 final long endTime = SystemClock.elapsedRealtime();
419 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
420 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
421 }
422
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800423 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700424 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800425 }
426 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700427 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800428 }
429
430 return events.toArray(new NativeDaemonEvent[events.size()]);
431 }
432
433 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800434 * Append the given argument to {@link StringBuilder}, escaping as needed,
435 * and surrounding with quotes when it contains spaces.
436 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800437 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800438 static void appendEscaped(StringBuilder builder, String arg) {
439 final boolean hasSpaces = arg.indexOf(' ') >= 0;
440 if (hasSpaces) {
441 builder.append('"');
442 }
443
444 final int length = arg.length();
445 for (int i = 0; i < length; i++) {
446 final char c = arg.charAt(i);
447
448 if (c == '"') {
449 builder.append("\\\"");
450 } else if (c == '\\') {
451 builder.append("\\\\");
452 } else {
453 builder.append(c);
454 }
455 }
456
457 if (hasSpaces) {
458 builder.append('"');
459 }
460 }
461
462 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
463 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
464 super(command, event);
465 }
466
467 @Override
468 public IllegalArgumentException rethrowAsParcelableException() {
469 throw new IllegalArgumentException(getMessage(), this);
470 }
471 }
472
473 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
474 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
475 super(command, event);
476 }
San Mehatdeba6932010-01-20 15:14:31 -0800477 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700478
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800479 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800480 * Command builder that handles argument list building. Any arguments must
481 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800482 */
483 public static class Command {
484 private String mCmd;
485 private ArrayList<Object> mArguments = Lists.newArrayList();
486
487 public Command(String cmd, Object... args) {
488 mCmd = cmd;
489 for (Object arg : args) {
490 appendArg(arg);
491 }
492 }
493
494 public Command appendArg(Object arg) {
495 mArguments.add(arg);
496 return this;
497 }
498 }
499
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700500 /** {@inheritDoc} */
501 public void monitor() {
502 synchronized (mDaemonLock) { }
503 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800504
505 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
506 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800507 pw.println();
508 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800509 }
510
511 private void log(String logstring) {
512 if (LOGD) Slog.d(TAG, logstring);
513 mLocalLog.log(logstring);
514 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800515
516 private void loge(String logstring) {
517 Slog.e(TAG, logstring);
518 mLocalLog.log(logstring);
519 }
520
521 private static class ResponseQueue {
522
Robert Greenwaltef215992012-06-05 11:48:40 -0700523 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700524 public final int cmdNum;
525 public final String logCmd;
526
Robert Greenwaltef215992012-06-05 11:48:40 -0700527 public BlockingQueue<NativeDaemonEvent> responses =
528 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700529
530 // The availableResponseCount member is used to track when we can remove this
531 // instance from the ResponseQueue.
532 // This is used under the protection of a sync of the mPendingCmds object.
533 // A positive value means we've had more writers retreive this object while
534 // a negative value means we've had more readers. When we've had an equal number
535 // (it goes to zero) we can remove this object from the mPendingCmds list.
536 // Note that we may have more responses for this command (and more readers
537 // coming), but that would result in a new PendingCmd instance being created
538 // and added with the same cmdNum.
539 // Also note that when this goes to zero it just means a parity of readers and
540 // writers have retrieved this object - not that they are done using it. The
541 // responses queue may well have more responses yet to be read or may get more
542 // responses added to it. But all those readers/writers have retreived and
543 // hold references to this instance already so it can be removed from
544 // mPendingCmds queue.
545 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700546
547 public PendingCmd(int cmdNum, String logCmd) {
548 this.cmdNum = cmdNum;
549 this.logCmd = logCmd;
550 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800551 }
552
Robert Greenwaltef215992012-06-05 11:48:40 -0700553 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800554 private int mMaxCount;
555
556 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700557 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800558 mMaxCount = maxCount;
559 }
560
561 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700562 PendingCmd found = null;
563 synchronized (mPendingCmds) {
564 for (PendingCmd pendingCmd : mPendingCmds) {
565 if (pendingCmd.cmdNum == cmdNum) {
566 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800567 break;
568 }
569 }
570 if (found == null) {
571 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700572 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800573 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700574 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800575 " >= " + mMaxCount);
576 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700577 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800578 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700579 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700580 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800581 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700582 found = new PendingCmd(cmdNum, null);
583 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800584 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700585 found.availableResponseCount++;
586 // if a matching remove call has already retrieved this we can remove this
587 // instance from our list
588 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800589 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700590 try {
591 found.responses.put(response);
592 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800593 }
594
595 // note that the timeout does not count time in deep sleep. If you don't want
596 // the device to sleep, hold a wakelock
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700597 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700598 PendingCmd found = null;
599 synchronized (mPendingCmds) {
600 for (PendingCmd pendingCmd : mPendingCmds) {
601 if (pendingCmd.cmdNum == cmdNum) {
602 found = pendingCmd;
603 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800604 }
605 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700606 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700607 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700608 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800609 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700610 found.availableResponseCount--;
611 // if a matching add call has already retrieved this we can remove this
612 // instance from our list
613 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800614 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700615 NativeDaemonEvent result = null;
616 try {
617 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
618 } catch (InterruptedException e) {}
619 if (result == null) {
620 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
621 }
622 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800623 }
624
625 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
626 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700627 synchronized (mPendingCmds) {
628 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700629 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800630 }
631 }
632 }
633 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800634}