blob: 62eb6632eb5a15bce366f1920611785925b924f1 [file] [log] [blame]
San Mehat67bd2cd2010-01-12 12:18:49 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
San Mehat67bd2cd2010-01-12 12:18:49 -080019import android.net.LocalSocket;
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070020import android.net.LocalSocketAddress;
Lorenzo Colitti7421a012013-08-20 22:51:24 +090021import android.os.Build;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070022import android.os.Handler;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070023import android.os.Message;
Dianne Hackborn77b987f2014-02-26 16:20:52 -080024import android.os.PowerManager;
San Mehat67bd2cd2010-01-12 12:18:49 -080025import android.os.SystemClock;
Robert Greenwalt470fd722012-01-18 12:51:15 -080026import android.util.LocalLog;
Joe Onorato8a9b2202010-02-26 18:56:32 -080027import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080028
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080029import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080030import com.google.android.collect.Lists;
31
Robert Greenwalt470fd722012-01-18 12:51:15 -080032import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080033import java.io.IOException;
34import java.io.InputStream;
35import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080036import java.io.PrintWriter;
Elliott Hughesd396a442013-06-28 16:24:48 -070037import java.nio.charset.StandardCharsets;
San Mehat67bd2cd2010-01-12 12:18:49 -080038import java.util.ArrayList;
Robert Greenwalt470007f2012-02-07 11:36:55 -080039import java.util.concurrent.atomic.AtomicInteger;
Robert Greenwaltef215992012-06-05 11:48:40 -070040import java.util.concurrent.ArrayBlockingQueue;
41import java.util.concurrent.BlockingQueue;
42import java.util.concurrent.TimeUnit;
Robert Greenwalt470007f2012-02-07 11:36:55 -080043import java.util.LinkedList;
San Mehat67bd2cd2010-01-12 12:18:49 -080044
45/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080046 * Generic connector class for interfacing with a native daemon which uses the
47 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080048 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070049final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Jeff Sharkey31c6e482011-11-18 17:09:01 -080050 private static final boolean LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080051
Jeff Sharkey31c6e482011-11-18 17:09:01 -080052 private final String TAG;
53
54 private String mSocket;
55 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080056 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080057
Robert Greenwalt470007f2012-02-07 11:36:55 -080058 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080059
Dianne Hackborn77b987f2014-02-26 16:20:52 -080060 private final PowerManager.WakeLock mWakeLock;
61
San Mehat67bd2cd2010-01-12 12:18:49 -080062 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080063 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080064
Robert Greenwalt470007f2012-02-07 11:36:55 -080065 private AtomicInteger mSequenceNumber;
66
67 private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070068 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080069
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070070 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080071 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070072
Kenny Root961aa8c2010-03-22 18:02:45 -070073 private final int BUFFER_SIZE = 4096;
74
Jeff Sharkey31c6e482011-11-18 17:09:01 -080075 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Dianne Hackborn77b987f2014-02-26 16:20:52 -080076 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
San Mehat67bd2cd2010-01-12 12:18:49 -080077 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080078 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080079 mResponseQueue = new ResponseQueue(responseQueueSize);
Dianne Hackborn77b987f2014-02-26 16:20:52 -080080 mWakeLock = wl;
81 if (mWakeLock != null) {
82 mWakeLock.setReferenceCounted(true);
83 }
Robert Greenwalt470007f2012-02-07 11:36:55 -080084 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -080085 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -080086 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -080087 }
88
Chia-chi Yehe5750a32011-08-03 14:42:11 -070089 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080090 public void run() {
Dianne Hackborn8d044e82013-04-30 17:24:15 -070091 mCallbackHandler = new Handler(FgThread.get().getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080092
93 while (true) {
94 try {
95 listenToSocket();
96 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -080097 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -080098 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080099 }
100 }
101 }
102
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700103 @Override
104 public boolean handleMessage(Message msg) {
105 String event = (String) msg.obj;
106 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700107 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800108 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700109 }
110 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800111 loge("Error handling '" + event + "': " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800112 } finally {
Dianne Hackborn4590e522014-03-24 13:36:46 -0700113 if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800114 mWakeLock.release();
115 }
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700116 }
117 return true;
118 }
119
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900120 private LocalSocketAddress determineSocketAddress() {
121 // If we're testing, set up a socket in a namespace that's accessible to test code.
122 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
123 // production devices, even if said native daemons ill-advisedly pick a socket name that
124 // starts with __test__, only allow this on debug builds.
125 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
126 return new LocalSocketAddress(mSocket);
127 } else {
128 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
129 }
130 }
131
San Mehat4c27e0e2010-01-29 05:22:17 -0800132 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700133 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800134
135 try {
136 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900137 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800138
139 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800140
141 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800142 synchronized (mDaemonLock) {
143 mOutputStream = socket.getOutputStream();
144 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800145
anga030bc882011-02-01 14:10:25 +0100146 mCallbacks.onDaemonConnected();
147
Kenny Root961aa8c2010-03-22 18:02:45 -0700148 byte[] buffer = new byte[BUFFER_SIZE];
149 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800150
151 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700152 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800153 if (count < 0) {
154 loge("got " + count + " reading with start = " + start);
155 break;
156 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800157
Kenny Root12da9d72010-09-02 22:18:14 -0700158 // Add our starting point to the count and reset the start.
159 count += start;
160 start = 0;
161
San Mehat67bd2cd2010-01-12 12:18:49 -0800162 for (int i = 0; i < count; i++) {
163 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800164 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700165 buffer, start, i - start, StandardCharsets.UTF_8);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800166 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800167
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800168 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800169 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800170 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
171 rawEvent);
172 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800173 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700174 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
175 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800176 mWakeLock.acquire();
177 releaseWl = true;
178 }
179 if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
180 event.getCode(), event.getRawEvent()))) {
181 releaseWl = false;
182 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800183 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800184 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800185 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800186 } catch (IllegalArgumentException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800187 log("Problem parsing message: " + rawEvent + " - " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800188 } finally {
189 if (releaseWl) {
190 mWakeLock.acquire();
191 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800192 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800193
San Mehat67bd2cd2010-01-12 12:18:49 -0800194 start = i + 1;
195 }
196 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800197 if (start == 0) {
Elliott Hughesd396a442013-06-28 16:24:48 -0700198 final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800199 log("RCV incomplete <- {" + rawEvent + "}");
200 }
Kenny Root12da9d72010-09-02 22:18:14 -0700201
202 // We should end at the amount we read. If not, compact then
203 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700204 if (start != count) {
205 final int remaining = BUFFER_SIZE - start;
206 System.arraycopy(buffer, start, buffer, 0, remaining);
207 start = remaining;
208 } else {
209 start = 0;
210 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800211 }
212 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800213 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800214 throw ex;
215 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700216 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800217 if (mOutputStream != null) {
218 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800219 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800220 mOutputStream.close();
221 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800222 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800223 }
224 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800225 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800226 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800227
San Mehat4c27e0e2010-01-29 05:22:17 -0800228 try {
229 if (socket != null) {
230 socket.close();
231 }
232 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800233 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800234 }
235 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800236 }
237
San Mehat67bd2cd2010-01-12 12:18:49 -0800238 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700239 * Wrapper around argument that indicates it's sensitive and shouldn't be
240 * logged.
241 */
242 public static class SensitiveArg {
243 private final Object mArg;
244
245 public SensitiveArg(Object arg) {
246 mArg = arg;
247 }
248
249 @Override
250 public String toString() {
251 return String.valueOf(mArg);
252 }
253 }
254
255 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800256 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800257 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700258 @VisibleForTesting
259 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
260 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800261 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800262 throw new IllegalArgumentException("Unexpected command: " + cmd);
263 }
264 if (cmd.indexOf(' ') >= 0) {
265 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700266 }
267
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700268 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
269 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800270 for (Object arg : args) {
271 final String argString = String.valueOf(arg);
272 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800273 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700274 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800275
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700276 rawBuilder.append(' ');
277 logBuilder.append(' ');
278
279 appendEscaped(rawBuilder, argString);
280 if (arg instanceof SensitiveArg) {
281 logBuilder.append("[scrubbed]");
282 } else {
283 appendEscaped(logBuilder, argString);
284 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800285 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700286
287 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800288 }
San Mehatdeba6932010-01-20 15:14:31 -0800289
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700290 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800291 * Issue the given command to the native daemon and return a single expected
292 * response.
293 *
294 * @throws NativeDaemonConnectorException when problem communicating with
295 * native daemon, or if the response matches
296 * {@link NativeDaemonEvent#isClassClientError()} or
297 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800298 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800299 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
300 return execute(cmd.mCmd, cmd.mArguments.toArray());
301 }
302
303 /**
304 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800305 * response. Any arguments must be separated from base command so they can
306 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800307 *
308 * @throws NativeDaemonConnectorException when problem communicating with
309 * native daemon, or if the response matches
310 * {@link NativeDaemonEvent#isClassClientError()} or
311 * {@link NativeDaemonEvent#isClassServerError()}.
312 */
313 public NativeDaemonEvent execute(String cmd, Object... args)
314 throws NativeDaemonConnectorException {
315 final NativeDaemonEvent[] events = executeForList(cmd, args);
316 if (events.length != 1) {
317 throw new NativeDaemonConnectorException(
318 "Expected exactly one response, but received " + events.length);
319 }
320 return events[0];
321 }
322
323 /**
324 * Issue the given command to the native daemon and return any
325 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
326 * final terminal response.
327 *
328 * @throws NativeDaemonConnectorException when problem communicating with
329 * native daemon, or if the response matches
330 * {@link NativeDaemonEvent#isClassClientError()} or
331 * {@link NativeDaemonEvent#isClassServerError()}.
332 */
333 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
334 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
335 }
336
337 /**
338 * Issue the given command to the native daemon and return any
339 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800340 * final terminal response. Any arguments must be separated from base
341 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800342 *
343 * @throws NativeDaemonConnectorException when problem communicating with
344 * native daemon, or if the response matches
345 * {@link NativeDaemonEvent#isClassClientError()} or
346 * {@link NativeDaemonEvent#isClassServerError()}.
347 */
348 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800349 throws NativeDaemonConnectorException {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800350 return execute(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800351 }
San Mehatdeba6932010-01-20 15:14:31 -0800352
Robert Greenwalt470007f2012-02-07 11:36:55 -0800353 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800354 * Issue the given command to the native daemon and return any {@linke
355 * NativeDaemonEvent@isClassContinue()} responses, including the final
356 * terminal response. Note that the timeout does not count time in deep
357 * sleep. Any arguments must be separated from base command so they can be
358 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -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[] execute(int timeout, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800366 throws NativeDaemonConnectorException {
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700367 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700368
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700369 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700370
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700371 final StringBuilder rawBuilder = new StringBuilder();
372 final StringBuilder logBuilder = new StringBuilder();
373 final int sequenceNumber = mSequenceNumber.incrementAndGet();
374
375 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
376
377 final String rawCmd = rawBuilder.toString();
378 final String logCmd = logBuilder.toString();
379
Robert Greenwaltd1925982012-03-12 15:37:40 -0700380 log("SND -> {" + logCmd + "}");
381
Robert Greenwaltd1925982012-03-12 15:37:40 -0700382 synchronized (mDaemonLock) {
383 if (mOutputStream == null) {
384 throw new NativeDaemonConnectorException("missing output stream");
385 } else {
386 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700387 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700388 } catch (IOException e) {
389 throw new NativeDaemonConnectorException("problem sending command", e);
390 }
391 }
392 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800393
394 NativeDaemonEvent event = null;
395 do {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700396 event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800397 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700398 loge("timed-out waiting for response to " + logCmd);
399 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800400 }
Robert Greenwaltb5aff3f2012-05-15 17:26:57 -0700401 log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800402 events.add(event);
403 } while (event.isClassContinue());
404
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700405 final long endTime = SystemClock.elapsedRealtime();
406 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
407 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
408 }
409
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800410 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700411 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800412 }
413 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700414 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800415 }
416
417 return events.toArray(new NativeDaemonEvent[events.size()]);
418 }
419
420 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800421 * Append the given argument to {@link StringBuilder}, escaping as needed,
422 * and surrounding with quotes when it contains spaces.
423 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800424 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800425 static void appendEscaped(StringBuilder builder, String arg) {
426 final boolean hasSpaces = arg.indexOf(' ') >= 0;
427 if (hasSpaces) {
428 builder.append('"');
429 }
430
431 final int length = arg.length();
432 for (int i = 0; i < length; i++) {
433 final char c = arg.charAt(i);
434
435 if (c == '"') {
436 builder.append("\\\"");
437 } else if (c == '\\') {
438 builder.append("\\\\");
439 } else {
440 builder.append(c);
441 }
442 }
443
444 if (hasSpaces) {
445 builder.append('"');
446 }
447 }
448
449 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
450 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
451 super(command, event);
452 }
453
454 @Override
455 public IllegalArgumentException rethrowAsParcelableException() {
456 throw new IllegalArgumentException(getMessage(), this);
457 }
458 }
459
460 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
461 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
462 super(command, event);
463 }
San Mehatdeba6932010-01-20 15:14:31 -0800464 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700465
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800466 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800467 * Command builder that handles argument list building. Any arguments must
468 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800469 */
470 public static class Command {
471 private String mCmd;
472 private ArrayList<Object> mArguments = Lists.newArrayList();
473
474 public Command(String cmd, Object... args) {
475 mCmd = cmd;
476 for (Object arg : args) {
477 appendArg(arg);
478 }
479 }
480
481 public Command appendArg(Object arg) {
482 mArguments.add(arg);
483 return this;
484 }
485 }
486
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700487 /** {@inheritDoc} */
488 public void monitor() {
489 synchronized (mDaemonLock) { }
490 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800491
492 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
493 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800494 pw.println();
495 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800496 }
497
498 private void log(String logstring) {
499 if (LOGD) Slog.d(TAG, logstring);
500 mLocalLog.log(logstring);
501 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800502
503 private void loge(String logstring) {
504 Slog.e(TAG, logstring);
505 mLocalLog.log(logstring);
506 }
507
508 private static class ResponseQueue {
509
Robert Greenwaltef215992012-06-05 11:48:40 -0700510 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700511 public final int cmdNum;
512 public final String logCmd;
513
Robert Greenwaltef215992012-06-05 11:48:40 -0700514 public BlockingQueue<NativeDaemonEvent> responses =
515 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700516
517 // The availableResponseCount member is used to track when we can remove this
518 // instance from the ResponseQueue.
519 // This is used under the protection of a sync of the mPendingCmds object.
520 // A positive value means we've had more writers retreive this object while
521 // a negative value means we've had more readers. When we've had an equal number
522 // (it goes to zero) we can remove this object from the mPendingCmds list.
523 // Note that we may have more responses for this command (and more readers
524 // coming), but that would result in a new PendingCmd instance being created
525 // and added with the same cmdNum.
526 // Also note that when this goes to zero it just means a parity of readers and
527 // writers have retrieved this object - not that they are done using it. The
528 // responses queue may well have more responses yet to be read or may get more
529 // responses added to it. But all those readers/writers have retreived and
530 // hold references to this instance already so it can be removed from
531 // mPendingCmds queue.
532 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700533
534 public PendingCmd(int cmdNum, String logCmd) {
535 this.cmdNum = cmdNum;
536 this.logCmd = logCmd;
537 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800538 }
539
Robert Greenwaltef215992012-06-05 11:48:40 -0700540 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800541 private int mMaxCount;
542
543 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700544 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800545 mMaxCount = maxCount;
546 }
547
548 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700549 PendingCmd found = null;
550 synchronized (mPendingCmds) {
551 for (PendingCmd pendingCmd : mPendingCmds) {
552 if (pendingCmd.cmdNum == cmdNum) {
553 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800554 break;
555 }
556 }
557 if (found == null) {
558 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700559 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800560 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700561 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800562 " >= " + mMaxCount);
563 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700564 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800565 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700566 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700567 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800568 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700569 found = new PendingCmd(cmdNum, null);
570 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800571 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700572 found.availableResponseCount++;
573 // if a matching remove call has already retrieved this we can remove this
574 // instance from our list
575 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800576 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700577 try {
578 found.responses.put(response);
579 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800580 }
581
582 // note that the timeout does not count time in deep sleep. If you don't want
583 // the device to sleep, hold a wakelock
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700584 public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700585 PendingCmd found = null;
586 synchronized (mPendingCmds) {
587 for (PendingCmd pendingCmd : mPendingCmds) {
588 if (pendingCmd.cmdNum == cmdNum) {
589 found = pendingCmd;
590 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800591 }
592 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700593 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700594 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700595 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800596 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700597 found.availableResponseCount--;
598 // if a matching add call has already retrieved this we can remove this
599 // instance from our list
600 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800601 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700602 NativeDaemonEvent result = null;
603 try {
604 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
605 } catch (InterruptedException e) {}
606 if (result == null) {
607 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
608 }
609 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800610 }
611
612 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
613 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700614 synchronized (mPendingCmds) {
615 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700616 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800617 }
618 }
619 }
620 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800621}