blob: 7db9be20e95f1dd81ddeda084f39f6512d5322d1 [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 Sharkey8948c012015-11-03 12:33:54 -080031import com.android.internal.util.Preconditions;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080032import com.google.android.collect.Lists;
33
Robert Greenwalt470fd722012-01-18 12:51:15 -080034import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080035import java.io.IOException;
36import java.io.InputStream;
37import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080038import java.io.PrintWriter;
Elliott Hughesd396a442013-06-28 16:24:48 -070039import java.nio.charset.StandardCharsets;
San Mehat67bd2cd2010-01-12 12:18:49 -080040import java.util.ArrayList;
Robert Greenwalt470007f2012-02-07 11:36:55 -080041import java.util.concurrent.atomic.AtomicInteger;
Robert Greenwaltef215992012-06-05 11:48:40 -070042import java.util.concurrent.ArrayBlockingQueue;
43import java.util.concurrent.BlockingQueue;
44import java.util.concurrent.TimeUnit;
Robert Greenwalt470007f2012-02-07 11:36:55 -080045import java.util.LinkedList;
San Mehat67bd2cd2010-01-12 12:18:49 -080046
47/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080048 * Generic connector class for interfacing with a native daemon which uses the
49 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080050 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070051final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Robert Greenwalt7f44ff82014-05-07 23:49:08 -070052 private final static boolean VDBG = false;
53
Jeff Sharkey31c6e482011-11-18 17:09:01 -080054 private final String TAG;
55
56 private String mSocket;
57 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080058 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080059
Jeff Sharkey48877892015-03-18 11:27:19 -070060 private volatile boolean mDebug = false;
Jeff Sharkey8948c012015-11-03 12:33:54 -080061 private volatile Object mWarnIfHeld;
Jeff Sharkey48877892015-03-18 11:27:19 -070062
Robert Greenwalt470007f2012-02-07 11:36:55 -080063 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080064
Dianne Hackborn77b987f2014-02-26 16:20:52 -080065 private final PowerManager.WakeLock mWakeLock;
66
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070067 private final Looper mLooper;
68
San Mehat67bd2cd2010-01-12 12:18:49 -080069 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080070 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080071
Robert Greenwalt470007f2012-02-07 11:36:55 -080072 private AtomicInteger mSequenceNumber;
73
Jeff Sharkey14cbe522015-07-08 14:06:37 -070074 private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070075 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080076
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070077 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080078 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070079
Kenny Root961aa8c2010-03-22 18:02:45 -070080 private final int BUFFER_SIZE = 4096;
81
Jeff Sharkey31c6e482011-11-18 17:09:01 -080082 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Dianne Hackborn77b987f2014-02-26 16:20:52 -080083 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070084 this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
85 FgThread.get().getLooper());
86 }
87
88 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
89 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
90 Looper looper) {
San Mehat67bd2cd2010-01-12 12:18:49 -080091 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080092 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080093 mResponseQueue = new ResponseQueue(responseQueueSize);
Dianne Hackborn77b987f2014-02-26 16:20:52 -080094 mWakeLock = wl;
95 if (mWakeLock != null) {
96 mWakeLock.setReferenceCounted(true);
97 }
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070098 mLooper = looper;
Robert Greenwalt470007f2012-02-07 11:36:55 -080099 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800100 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -0800101 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -0800102 }
103
Jeff Sharkey48877892015-03-18 11:27:19 -0700104 /**
105 * Enable Set debugging mode, which causes messages to also be written to both
106 * {@link Slog} in addition to internal log.
107 */
108 public void setDebug(boolean debug) {
109 mDebug = debug;
110 }
111
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800112 /**
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900113 * Like SystemClock.uptimeMillis, except truncated to an int so it will fit in a message arg.
114 * Inaccurate across 49.7 days of uptime, but only used for debugging.
115 */
116 private int uptimeMillisInt() {
117 return (int) SystemClock.uptimeMillis() & Integer.MAX_VALUE;
118 }
119
120 /**
Jeff Sharkey8948c012015-11-03 12:33:54 -0800121 * Yell loudly if someone tries making future {@link #execute(Command)}
122 * calls while holding a lock on the given object.
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800123 */
Jeff Sharkey8948c012015-11-03 12:33:54 -0800124 public void setWarnIfHeld(Object warnIfHeld) {
125 Preconditions.checkState(mWarnIfHeld == null);
126 mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800127 }
128
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700129 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -0800130 public void run() {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -0700131 mCallbackHandler = new Handler(mLooper, this);
San Mehat67bd2cd2010-01-12 12:18:49 -0800132
133 while (true) {
134 try {
135 listenToSocket();
136 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800137 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800138 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -0800139 }
140 }
141 }
142
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700143 @Override
144 public boolean handleMessage(Message msg) {
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900145 final String event = (String) msg.obj;
146 final int start = uptimeMillisInt();
147 final int sent = msg.arg1;
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700148 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700149 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800150 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700151 }
152 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800153 loge("Error handling '" + event + "': " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800154 } finally {
Dianne Hackborn4590e522014-03-24 13:36:46 -0700155 if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800156 mWakeLock.release();
157 }
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900158 final int end = uptimeMillisInt();
159 if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
160 loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
161 }
162 if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
163 loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
164 }
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700165 }
166 return true;
167 }
168
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900169 private LocalSocketAddress determineSocketAddress() {
170 // If we're testing, set up a socket in a namespace that's accessible to test code.
171 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
172 // production devices, even if said native daemons ill-advisedly pick a socket name that
173 // starts with __test__, only allow this on debug builds.
174 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
175 return new LocalSocketAddress(mSocket);
176 } else {
177 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
178 }
179 }
180
San Mehat4c27e0e2010-01-29 05:22:17 -0800181 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700182 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800183
184 try {
185 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900186 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800187
188 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800189
190 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800191 synchronized (mDaemonLock) {
192 mOutputStream = socket.getOutputStream();
193 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800194
anga030bc882011-02-01 14:10:25 +0100195 mCallbacks.onDaemonConnected();
196
Daichi Hironodda65522015-11-19 16:58:57 +0900197 FileDescriptor[] fdList = null;
Kenny Root961aa8c2010-03-22 18:02:45 -0700198 byte[] buffer = new byte[BUFFER_SIZE];
199 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800200
201 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700202 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800203 if (count < 0) {
204 loge("got " + count + " reading with start = " + start);
205 break;
206 }
Daichi Hironodda65522015-11-19 16:58:57 +0900207 fdList = socket.getAncillaryFileDescriptors();
San Mehat67bd2cd2010-01-12 12:18:49 -0800208
Kenny Root12da9d72010-09-02 22:18:14 -0700209 // Add our starting point to the count and reset the start.
210 count += start;
211 start = 0;
212
San Mehat67bd2cd2010-01-12 12:18:49 -0800213 for (int i = 0; i < count; i++) {
214 if (buffer[i] == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800215 // Note - do not log this raw message since it may contain
216 // sensitive data
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800217 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700218 buffer, start, i - start, StandardCharsets.UTF_8);
San Mehat67bd2cd2010-01-12 12:18:49 -0800219
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800220 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800221 try {
Daichi Hironodda65522015-11-19 16:58:57 +0900222 final NativeDaemonEvent event =
223 NativeDaemonEvent.parseRawEvent(rawEvent, fdList);
Paul Lawrencec38182f2014-11-11 12:23:22 -0800224
225 log("RCV <- {" + event + "}");
226
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800227 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800228 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700229 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
230 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800231 mWakeLock.acquire();
232 releaseWl = true;
233 }
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900234 Message msg = mCallbackHandler.obtainMessage(
235 event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
236 if (mCallbackHandler.sendMessage(msg)) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800237 releaseWl = false;
238 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800239 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800240 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800241 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800242 } catch (IllegalArgumentException e) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800243 log("Problem parsing message " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800244 } finally {
245 if (releaseWl) {
246 mWakeLock.acquire();
247 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800248 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800249
San Mehat67bd2cd2010-01-12 12:18:49 -0800250 start = i + 1;
251 }
252 }
Paul Lawrencec38182f2014-11-11 12:23:22 -0800253
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800254 if (start == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800255 log("RCV incomplete");
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800256 }
Kenny Root12da9d72010-09-02 22:18:14 -0700257
258 // We should end at the amount we read. If not, compact then
259 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700260 if (start != count) {
261 final int remaining = BUFFER_SIZE - start;
262 System.arraycopy(buffer, start, buffer, 0, remaining);
263 start = remaining;
264 } else {
265 start = 0;
266 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800267 }
268 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800269 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800270 throw ex;
271 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700272 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800273 if (mOutputStream != null) {
274 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800275 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800276 mOutputStream.close();
277 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800278 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800279 }
280 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800281 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800282 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800283
San Mehat4c27e0e2010-01-29 05:22:17 -0800284 try {
285 if (socket != null) {
286 socket.close();
287 }
288 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800289 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800290 }
291 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800292 }
293
San Mehat67bd2cd2010-01-12 12:18:49 -0800294 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700295 * Wrapper around argument that indicates it's sensitive and shouldn't be
296 * logged.
297 */
298 public static class SensitiveArg {
299 private final Object mArg;
300
301 public SensitiveArg(Object arg) {
302 mArg = arg;
303 }
304
305 @Override
306 public String toString() {
307 return String.valueOf(mArg);
308 }
309 }
310
311 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800312 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800313 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700314 @VisibleForTesting
315 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
316 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800317 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800318 throw new IllegalArgumentException("Unexpected command: " + cmd);
319 }
320 if (cmd.indexOf(' ') >= 0) {
321 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700322 }
323
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700324 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
325 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800326 for (Object arg : args) {
327 final String argString = String.valueOf(arg);
328 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800329 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700330 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800331
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700332 rawBuilder.append(' ');
333 logBuilder.append(' ');
334
335 appendEscaped(rawBuilder, argString);
336 if (arg instanceof SensitiveArg) {
337 logBuilder.append("[scrubbed]");
338 } else {
339 appendEscaped(logBuilder, argString);
340 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800341 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700342
343 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800344 }
San Mehatdeba6932010-01-20 15:14:31 -0800345
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700346 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800347 * Issue the given command to the native daemon and return a single expected
348 * response.
349 *
350 * @throws NativeDaemonConnectorException when problem communicating with
351 * native daemon, or if the response matches
352 * {@link NativeDaemonEvent#isClassClientError()} or
353 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800354 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800355 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
356 return execute(cmd.mCmd, cmd.mArguments.toArray());
357 }
358
359 /**
360 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800361 * response. Any arguments must be separated from base command so they can
362 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800363 *
364 * @throws NativeDaemonConnectorException when problem communicating with
365 * native daemon, or if the response matches
366 * {@link NativeDaemonEvent#isClassClientError()} or
367 * {@link NativeDaemonEvent#isClassServerError()}.
368 */
369 public NativeDaemonEvent execute(String cmd, Object... args)
370 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700371 return execute(DEFAULT_TIMEOUT, cmd, args);
372 }
373
374 public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
375 throws NativeDaemonConnectorException {
376 final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800377 if (events.length != 1) {
378 throw new NativeDaemonConnectorException(
379 "Expected exactly one response, but received " + events.length);
380 }
381 return events[0];
382 }
383
384 /**
385 * Issue the given command to the native daemon and return any
386 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
387 * final terminal response.
388 *
389 * @throws NativeDaemonConnectorException when problem communicating with
390 * native daemon, or if the response matches
391 * {@link NativeDaemonEvent#isClassClientError()} or
392 * {@link NativeDaemonEvent#isClassServerError()}.
393 */
394 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
395 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
396 }
397
398 /**
399 * Issue the given command to the native daemon and return any
400 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800401 * final terminal response. Any arguments must be separated from base
402 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800403 *
404 * @throws NativeDaemonConnectorException when problem communicating with
405 * native daemon, or if the response matches
406 * {@link NativeDaemonEvent#isClassClientError()} or
407 * {@link NativeDaemonEvent#isClassServerError()}.
408 */
409 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800410 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700411 return executeForList(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800412 }
San Mehatdeba6932010-01-20 15:14:31 -0800413
Robert Greenwalt470007f2012-02-07 11:36:55 -0800414 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800415 * Issue the given command to the native daemon and return any {@linke
416 * NativeDaemonEvent@isClassContinue()} responses, including the final
417 * terminal response. Note that the timeout does not count time in deep
418 * sleep. Any arguments must be separated from base command so they can be
419 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800420 *
421 * @throws NativeDaemonConnectorException when problem communicating with
422 * native daemon, or if the response matches
423 * {@link NativeDaemonEvent#isClassClientError()} or
424 * {@link NativeDaemonEvent#isClassServerError()}.
425 */
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700426 public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800427 throws NativeDaemonConnectorException {
Jeff Sharkey8948c012015-11-03 12:33:54 -0800428 if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
429 Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
430 + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800431 }
432
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700433 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700434
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700435 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700436
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700437 final StringBuilder rawBuilder = new StringBuilder();
438 final StringBuilder logBuilder = new StringBuilder();
439 final int sequenceNumber = mSequenceNumber.incrementAndGet();
440
441 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
442
443 final String rawCmd = rawBuilder.toString();
444 final String logCmd = logBuilder.toString();
445
Robert Greenwaltd1925982012-03-12 15:37:40 -0700446 log("SND -> {" + logCmd + "}");
447
Robert Greenwaltd1925982012-03-12 15:37:40 -0700448 synchronized (mDaemonLock) {
449 if (mOutputStream == null) {
450 throw new NativeDaemonConnectorException("missing output stream");
451 } else {
452 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700453 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700454 } catch (IOException e) {
455 throw new NativeDaemonConnectorException("problem sending command", e);
456 }
457 }
458 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800459
460 NativeDaemonEvent event = null;
461 do {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700462 event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800463 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700464 loge("timed-out waiting for response to " + logCmd);
Todd Kennedy8101ee62015-06-23 13:35:28 -0700465 throw new NativeDaemonTimeoutException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800466 }
Robert Greenwalt7f44ff82014-05-07 23:49:08 -0700467 if (VDBG) log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800468 events.add(event);
469 } while (event.isClassContinue());
470
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700471 final long endTime = SystemClock.elapsedRealtime();
472 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
473 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
474 }
475
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800476 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700477 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800478 }
479 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700480 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800481 }
482
483 return events.toArray(new NativeDaemonEvent[events.size()]);
484 }
485
486 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800487 * Append the given argument to {@link StringBuilder}, escaping as needed,
488 * and surrounding with quotes when it contains spaces.
489 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800490 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800491 static void appendEscaped(StringBuilder builder, String arg) {
492 final boolean hasSpaces = arg.indexOf(' ') >= 0;
493 if (hasSpaces) {
494 builder.append('"');
495 }
496
497 final int length = arg.length();
498 for (int i = 0; i < length; i++) {
499 final char c = arg.charAt(i);
500
501 if (c == '"') {
502 builder.append("\\\"");
503 } else if (c == '\\') {
504 builder.append("\\\\");
505 } else {
506 builder.append(c);
507 }
508 }
509
510 if (hasSpaces) {
511 builder.append('"');
512 }
513 }
514
515 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
516 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
517 super(command, event);
518 }
519
520 @Override
521 public IllegalArgumentException rethrowAsParcelableException() {
522 throw new IllegalArgumentException(getMessage(), this);
523 }
524 }
525
526 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
527 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
528 super(command, event);
529 }
San Mehatdeba6932010-01-20 15:14:31 -0800530 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700531
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800532 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800533 * Command builder that handles argument list building. Any arguments must
534 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800535 */
536 public static class Command {
537 private String mCmd;
538 private ArrayList<Object> mArguments = Lists.newArrayList();
539
540 public Command(String cmd, Object... args) {
541 mCmd = cmd;
542 for (Object arg : args) {
543 appendArg(arg);
544 }
545 }
546
547 public Command appendArg(Object arg) {
548 mArguments.add(arg);
549 return this;
550 }
551 }
552
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700553 /** {@inheritDoc} */
554 public void monitor() {
555 synchronized (mDaemonLock) { }
556 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800557
558 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
559 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800560 pw.println();
561 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800562 }
563
564 private void log(String logstring) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700565 if (mDebug) Slog.d(TAG, logstring);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800566 mLocalLog.log(logstring);
567 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800568
569 private void loge(String logstring) {
570 Slog.e(TAG, logstring);
571 mLocalLog.log(logstring);
572 }
573
574 private static class ResponseQueue {
575
Robert Greenwaltef215992012-06-05 11:48:40 -0700576 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700577 public final int cmdNum;
578 public final String logCmd;
579
Robert Greenwaltef215992012-06-05 11:48:40 -0700580 public BlockingQueue<NativeDaemonEvent> responses =
581 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700582
583 // The availableResponseCount member is used to track when we can remove this
584 // instance from the ResponseQueue.
585 // This is used under the protection of a sync of the mPendingCmds object.
586 // A positive value means we've had more writers retreive this object while
587 // a negative value means we've had more readers. When we've had an equal number
588 // (it goes to zero) we can remove this object from the mPendingCmds list.
589 // Note that we may have more responses for this command (and more readers
590 // coming), but that would result in a new PendingCmd instance being created
591 // and added with the same cmdNum.
592 // Also note that when this goes to zero it just means a parity of readers and
593 // writers have retrieved this object - not that they are done using it. The
594 // responses queue may well have more responses yet to be read or may get more
595 // responses added to it. But all those readers/writers have retreived and
596 // hold references to this instance already so it can be removed from
597 // mPendingCmds queue.
598 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700599
600 public PendingCmd(int cmdNum, String logCmd) {
601 this.cmdNum = cmdNum;
602 this.logCmd = logCmd;
603 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800604 }
605
Robert Greenwaltef215992012-06-05 11:48:40 -0700606 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800607 private int mMaxCount;
608
609 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700610 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800611 mMaxCount = maxCount;
612 }
613
614 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700615 PendingCmd found = null;
616 synchronized (mPendingCmds) {
617 for (PendingCmd pendingCmd : mPendingCmds) {
618 if (pendingCmd.cmdNum == cmdNum) {
619 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800620 break;
621 }
622 }
623 if (found == null) {
624 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700625 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800626 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700627 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800628 " >= " + mMaxCount);
629 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700630 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800631 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700632 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700633 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800634 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700635 found = new PendingCmd(cmdNum, null);
636 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800637 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700638 found.availableResponseCount++;
639 // if a matching remove call has already retrieved this we can remove this
640 // instance from our list
641 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800642 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700643 try {
644 found.responses.put(response);
645 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800646 }
647
648 // note that the timeout does not count time in deep sleep. If you don't want
649 // the device to sleep, hold a wakelock
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700650 public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700651 PendingCmd found = null;
652 synchronized (mPendingCmds) {
653 for (PendingCmd pendingCmd : mPendingCmds) {
654 if (pendingCmd.cmdNum == cmdNum) {
655 found = pendingCmd;
656 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800657 }
658 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700659 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700660 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700661 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800662 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700663 found.availableResponseCount--;
664 // if a matching add call has already retrieved this we can remove this
665 // instance from our list
666 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800667 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700668 NativeDaemonEvent result = null;
669 try {
670 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
671 } catch (InterruptedException e) {}
672 if (result == null) {
673 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
674 }
675 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800676 }
677
678 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
679 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700680 synchronized (mPendingCmds) {
681 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700682 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800683 }
684 }
685 }
686 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800687}