blob: f5f7732141242105383e3804b6e6077f5d0b34f5 [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;
Rebecca Silbersteinefdb8452016-04-21 12:14:41 -070044import java.util.concurrent.CountDownLatch;
Robert Greenwaltef215992012-06-05 11:48:40 -070045import java.util.concurrent.TimeUnit;
Robert Greenwalt470007f2012-02-07 11:36:55 -080046import java.util.LinkedList;
San Mehat67bd2cd2010-01-12 12:18:49 -080047
48/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080049 * Generic connector class for interfacing with a native daemon which uses the
50 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080051 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070052final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
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
Jeff Sharkey48877892015-03-18 11:27:19 -070061 private volatile boolean mDebug = false;
Jeff Sharkey8948c012015-11-03 12:33:54 -080062 private volatile Object mWarnIfHeld;
Jeff Sharkey48877892015-03-18 11:27:19 -070063
Robert Greenwalt470007f2012-02-07 11:36:55 -080064 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080065
Dianne Hackborn77b987f2014-02-26 16:20:52 -080066 private final PowerManager.WakeLock mWakeLock;
67
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070068 private final Looper mLooper;
69
San Mehat67bd2cd2010-01-12 12:18:49 -080070 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080071 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080072
Robert Greenwalt470007f2012-02-07 11:36:55 -080073 private AtomicInteger mSequenceNumber;
74
Jeff Sharkey14cbe522015-07-08 14:06:37 -070075 private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070076 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080077
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070078 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080079 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070080
Kenny Root961aa8c2010-03-22 18:02:45 -070081 private final int BUFFER_SIZE = 4096;
82
Jeff Sharkey31c6e482011-11-18 17:09:01 -080083 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Dianne Hackborn77b987f2014-02-26 16:20:52 -080084 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070085 this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
86 FgThread.get().getLooper());
87 }
88
89 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
90 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
91 Looper looper) {
San Mehat67bd2cd2010-01-12 12:18:49 -080092 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080093 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080094 mResponseQueue = new ResponseQueue(responseQueueSize);
Dianne Hackborn77b987f2014-02-26 16:20:52 -080095 mWakeLock = wl;
96 if (mWakeLock != null) {
97 mWakeLock.setReferenceCounted(true);
98 }
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070099 mLooper = looper;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800100 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800101 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -0800102 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -0800103 }
104
Jeff Sharkey48877892015-03-18 11:27:19 -0700105 /**
106 * Enable Set debugging mode, which causes messages to also be written to both
107 * {@link Slog} in addition to internal log.
108 */
109 public void setDebug(boolean debug) {
110 mDebug = debug;
111 }
112
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800113 /**
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900114 * Like SystemClock.uptimeMillis, except truncated to an int so it will fit in a message arg.
115 * Inaccurate across 49.7 days of uptime, but only used for debugging.
116 */
117 private int uptimeMillisInt() {
118 return (int) SystemClock.uptimeMillis() & Integer.MAX_VALUE;
119 }
120
121 /**
Jeff Sharkey8948c012015-11-03 12:33:54 -0800122 * Yell loudly if someone tries making future {@link #execute(Command)}
123 * calls while holding a lock on the given object.
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800124 */
Jeff Sharkey8948c012015-11-03 12:33:54 -0800125 public void setWarnIfHeld(Object warnIfHeld) {
126 Preconditions.checkState(mWarnIfHeld == null);
127 mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800128 }
129
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700130 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -0800131 public void run() {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -0700132 mCallbackHandler = new Handler(mLooper, this);
San Mehat67bd2cd2010-01-12 12:18:49 -0800133
134 while (true) {
135 try {
136 listenToSocket();
137 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800138 loge("Error in NativeDaemonConnector: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800139 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -0800140 }
141 }
142 }
143
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700144 @Override
145 public boolean handleMessage(Message msg) {
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900146 final String event = (String) msg.obj;
147 final int start = uptimeMillisInt();
148 final int sent = msg.arg1;
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700149 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700150 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800151 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700152 }
153 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800154 loge("Error handling '" + event + "': " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800155 } finally {
Dianne Hackborn4590e522014-03-24 13:36:46 -0700156 if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800157 mWakeLock.release();
158 }
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900159 final int end = uptimeMillisInt();
160 if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
161 loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
162 }
163 if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
164 loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
165 }
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700166 }
167 return true;
168 }
169
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900170 private LocalSocketAddress determineSocketAddress() {
171 // If we're testing, set up a socket in a namespace that's accessible to test code.
172 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
173 // production devices, even if said native daemons ill-advisedly pick a socket name that
174 // starts with __test__, only allow this on debug builds.
175 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
176 return new LocalSocketAddress(mSocket);
177 } else {
178 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
179 }
180 }
181
San Mehat4c27e0e2010-01-29 05:22:17 -0800182 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700183 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800184
185 try {
186 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900187 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800188
189 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800190
191 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800192 synchronized (mDaemonLock) {
193 mOutputStream = socket.getOutputStream();
194 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800195
anga030bc882011-02-01 14:10:25 +0100196 mCallbacks.onDaemonConnected();
197
Daichi Hironodda65522015-11-19 16:58:57 +0900198 FileDescriptor[] fdList = null;
Kenny Root961aa8c2010-03-22 18:02:45 -0700199 byte[] buffer = new byte[BUFFER_SIZE];
200 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800201
202 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700203 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800204 if (count < 0) {
205 loge("got " + count + " reading with start = " + start);
206 break;
207 }
Daichi Hironodda65522015-11-19 16:58:57 +0900208 fdList = socket.getAncillaryFileDescriptors();
San Mehat67bd2cd2010-01-12 12:18:49 -0800209
Kenny Root12da9d72010-09-02 22:18:14 -0700210 // Add our starting point to the count and reset the start.
211 count += start;
212 start = 0;
213
San Mehat67bd2cd2010-01-12 12:18:49 -0800214 for (int i = 0; i < count; i++) {
215 if (buffer[i] == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800216 // Note - do not log this raw message since it may contain
217 // sensitive data
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800218 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700219 buffer, start, i - start, StandardCharsets.UTF_8);
San Mehat67bd2cd2010-01-12 12:18:49 -0800220
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800221 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800222 try {
Daichi Hironodda65522015-11-19 16:58:57 +0900223 final NativeDaemonEvent event =
224 NativeDaemonEvent.parseRawEvent(rawEvent, fdList);
Paul Lawrencec38182f2014-11-11 12:23:22 -0800225
226 log("RCV <- {" + event + "}");
227
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800228 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800229 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700230 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
231 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800232 mWakeLock.acquire();
233 releaseWl = true;
234 }
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900235 Message msg = mCallbackHandler.obtainMessage(
236 event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
237 if (mCallbackHandler.sendMessage(msg)) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800238 releaseWl = false;
239 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800240 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800241 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800242 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800243 } catch (IllegalArgumentException e) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800244 log("Problem parsing message " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800245 } finally {
246 if (releaseWl) {
Lorenzo Colittiae107af2016-04-11 17:08:10 +0900247 mWakeLock.release();
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800248 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800249 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800250
San Mehat67bd2cd2010-01-12 12:18:49 -0800251 start = i + 1;
252 }
253 }
Paul Lawrencec38182f2014-11-11 12:23:22 -0800254
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800255 if (start == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800256 log("RCV incomplete");
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800257 }
Kenny Root12da9d72010-09-02 22:18:14 -0700258
259 // We should end at the amount we read. If not, compact then
260 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700261 if (start != count) {
262 final int remaining = BUFFER_SIZE - start;
263 System.arraycopy(buffer, start, buffer, 0, remaining);
264 start = remaining;
265 } else {
266 start = 0;
267 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800268 }
269 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800270 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800271 throw ex;
272 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700273 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800274 if (mOutputStream != null) {
275 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800276 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800277 mOutputStream.close();
278 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800279 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800280 }
281 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800282 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800283 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800284
San Mehat4c27e0e2010-01-29 05:22:17 -0800285 try {
286 if (socket != null) {
287 socket.close();
288 }
289 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800290 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800291 }
292 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800293 }
294
San Mehat67bd2cd2010-01-12 12:18:49 -0800295 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700296 * Wrapper around argument that indicates it's sensitive and shouldn't be
297 * logged.
298 */
299 public static class SensitiveArg {
300 private final Object mArg;
301
302 public SensitiveArg(Object arg) {
303 mArg = arg;
304 }
305
306 @Override
307 public String toString() {
308 return String.valueOf(mArg);
309 }
310 }
311
312 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800313 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800314 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700315 @VisibleForTesting
316 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
317 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800318 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800319 throw new IllegalArgumentException("Unexpected command: " + cmd);
320 }
321 if (cmd.indexOf(' ') >= 0) {
322 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700323 }
324
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700325 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
326 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800327 for (Object arg : args) {
328 final String argString = String.valueOf(arg);
329 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800330 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700331 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800332
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700333 rawBuilder.append(' ');
334 logBuilder.append(' ');
335
336 appendEscaped(rawBuilder, argString);
337 if (arg instanceof SensitiveArg) {
338 logBuilder.append("[scrubbed]");
339 } else {
340 appendEscaped(logBuilder, argString);
341 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800342 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700343
344 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800345 }
San Mehatdeba6932010-01-20 15:14:31 -0800346
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700347 /**
Rebecca Silbersteinefdb8452016-04-21 12:14:41 -0700348 * Method that waits until all asychronous notifications sent by the native daemon have
349 * been processed. This method must not be called on the notification thread or an
350 * exception will be thrown.
351 */
352 public void waitForCallbacks() {
353 if (Thread.currentThread() == mLooper.getThread()) {
354 throw new IllegalStateException("Must not call this method on callback thread");
355 }
356
357 final CountDownLatch latch = new CountDownLatch(1);
358 mCallbackHandler.post(new Runnable() {
359 @Override
360 public void run() {
361 latch.countDown();
362 }
363 });
364 try {
365 latch.await();
366 } catch (InterruptedException e) {
367 Slog.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
368 }
369 }
370
371 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800372 * Issue the given command to the native daemon and return a single expected
373 * response.
374 *
375 * @throws NativeDaemonConnectorException when problem communicating with
376 * native daemon, or if the response matches
377 * {@link NativeDaemonEvent#isClassClientError()} or
378 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800379 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800380 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
381 return execute(cmd.mCmd, cmd.mArguments.toArray());
382 }
383
384 /**
385 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800386 * response. Any arguments must be separated from base command so they can
387 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800388 *
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 execute(String cmd, Object... args)
395 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700396 return execute(DEFAULT_TIMEOUT, cmd, args);
397 }
398
399 public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
400 throws NativeDaemonConnectorException {
401 final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800402 if (events.length != 1) {
403 throw new NativeDaemonConnectorException(
404 "Expected exactly one response, but received " + events.length);
405 }
406 return events[0];
407 }
408
409 /**
410 * Issue the given command to the native daemon and return any
411 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
412 * final terminal response.
413 *
414 * @throws NativeDaemonConnectorException when problem communicating with
415 * native daemon, or if the response matches
416 * {@link NativeDaemonEvent#isClassClientError()} or
417 * {@link NativeDaemonEvent#isClassServerError()}.
418 */
419 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
420 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
421 }
422
423 /**
424 * Issue the given command to the native daemon and return any
425 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800426 * final terminal response. Any arguments must be separated from base
427 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800428 *
429 * @throws NativeDaemonConnectorException when problem communicating with
430 * native daemon, or if the response matches
431 * {@link NativeDaemonEvent#isClassClientError()} or
432 * {@link NativeDaemonEvent#isClassServerError()}.
433 */
434 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800435 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700436 return executeForList(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800437 }
San Mehatdeba6932010-01-20 15:14:31 -0800438
Robert Greenwalt470007f2012-02-07 11:36:55 -0800439 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800440 * Issue the given command to the native daemon and return any {@linke
441 * NativeDaemonEvent@isClassContinue()} responses, including the final
442 * terminal response. Note that the timeout does not count time in deep
443 * sleep. Any arguments must be separated from base command so they can be
444 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800445 *
446 * @throws NativeDaemonConnectorException when problem communicating with
447 * native daemon, or if the response matches
448 * {@link NativeDaemonEvent#isClassClientError()} or
449 * {@link NativeDaemonEvent#isClassServerError()}.
450 */
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700451 public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800452 throws NativeDaemonConnectorException {
Jeff Sharkey8948c012015-11-03 12:33:54 -0800453 if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
454 Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
455 + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800456 }
457
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700458 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700459
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700460 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700461
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700462 final StringBuilder rawBuilder = new StringBuilder();
463 final StringBuilder logBuilder = new StringBuilder();
464 final int sequenceNumber = mSequenceNumber.incrementAndGet();
465
466 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
467
468 final String rawCmd = rawBuilder.toString();
469 final String logCmd = logBuilder.toString();
470
Robert Greenwaltd1925982012-03-12 15:37:40 -0700471 log("SND -> {" + logCmd + "}");
472
Robert Greenwaltd1925982012-03-12 15:37:40 -0700473 synchronized (mDaemonLock) {
474 if (mOutputStream == null) {
475 throw new NativeDaemonConnectorException("missing output stream");
476 } else {
477 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700478 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700479 } catch (IOException e) {
480 throw new NativeDaemonConnectorException("problem sending command", e);
481 }
482 }
483 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800484
485 NativeDaemonEvent event = null;
486 do {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700487 event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800488 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700489 loge("timed-out waiting for response to " + logCmd);
Todd Kennedy8101ee62015-06-23 13:35:28 -0700490 throw new NativeDaemonTimeoutException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800491 }
Robert Greenwalt7f44ff82014-05-07 23:49:08 -0700492 if (VDBG) log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800493 events.add(event);
494 } while (event.isClassContinue());
495
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700496 final long endTime = SystemClock.elapsedRealtime();
497 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
498 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
499 }
500
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800501 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700502 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800503 }
504 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700505 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800506 }
507
508 return events.toArray(new NativeDaemonEvent[events.size()]);
509 }
510
511 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800512 * Append the given argument to {@link StringBuilder}, escaping as needed,
513 * and surrounding with quotes when it contains spaces.
514 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800515 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800516 static void appendEscaped(StringBuilder builder, String arg) {
517 final boolean hasSpaces = arg.indexOf(' ') >= 0;
518 if (hasSpaces) {
519 builder.append('"');
520 }
521
522 final int length = arg.length();
523 for (int i = 0; i < length; i++) {
524 final char c = arg.charAt(i);
525
526 if (c == '"') {
527 builder.append("\\\"");
528 } else if (c == '\\') {
529 builder.append("\\\\");
530 } else {
531 builder.append(c);
532 }
533 }
534
535 if (hasSpaces) {
536 builder.append('"');
537 }
538 }
539
540 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
541 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
542 super(command, event);
543 }
544
545 @Override
546 public IllegalArgumentException rethrowAsParcelableException() {
547 throw new IllegalArgumentException(getMessage(), this);
548 }
549 }
550
551 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
552 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
553 super(command, event);
554 }
San Mehatdeba6932010-01-20 15:14:31 -0800555 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700556
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800557 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800558 * Command builder that handles argument list building. Any arguments must
559 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800560 */
561 public static class Command {
562 private String mCmd;
563 private ArrayList<Object> mArguments = Lists.newArrayList();
564
565 public Command(String cmd, Object... args) {
566 mCmd = cmd;
567 for (Object arg : args) {
568 appendArg(arg);
569 }
570 }
571
572 public Command appendArg(Object arg) {
573 mArguments.add(arg);
574 return this;
575 }
576 }
577
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700578 /** {@inheritDoc} */
579 public void monitor() {
580 synchronized (mDaemonLock) { }
581 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800582
583 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
584 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800585 pw.println();
586 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800587 }
588
589 private void log(String logstring) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700590 if (mDebug) Slog.d(TAG, logstring);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800591 mLocalLog.log(logstring);
592 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800593
594 private void loge(String logstring) {
595 Slog.e(TAG, logstring);
596 mLocalLog.log(logstring);
597 }
598
599 private static class ResponseQueue {
600
Robert Greenwaltef215992012-06-05 11:48:40 -0700601 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700602 public final int cmdNum;
603 public final String logCmd;
604
Robert Greenwaltef215992012-06-05 11:48:40 -0700605 public BlockingQueue<NativeDaemonEvent> responses =
606 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700607
608 // The availableResponseCount member is used to track when we can remove this
609 // instance from the ResponseQueue.
610 // This is used under the protection of a sync of the mPendingCmds object.
611 // A positive value means we've had more writers retreive this object while
612 // a negative value means we've had more readers. When we've had an equal number
613 // (it goes to zero) we can remove this object from the mPendingCmds list.
614 // Note that we may have more responses for this command (and more readers
615 // coming), but that would result in a new PendingCmd instance being created
616 // and added with the same cmdNum.
617 // Also note that when this goes to zero it just means a parity of readers and
618 // writers have retrieved this object - not that they are done using it. The
619 // responses queue may well have more responses yet to be read or may get more
620 // responses added to it. But all those readers/writers have retreived and
621 // hold references to this instance already so it can be removed from
622 // mPendingCmds queue.
623 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700624
625 public PendingCmd(int cmdNum, String logCmd) {
626 this.cmdNum = cmdNum;
627 this.logCmd = logCmd;
628 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800629 }
630
Robert Greenwaltef215992012-06-05 11:48:40 -0700631 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800632 private int mMaxCount;
633
634 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700635 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800636 mMaxCount = maxCount;
637 }
638
639 public void add(int cmdNum, NativeDaemonEvent response) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700640 PendingCmd found = null;
641 synchronized (mPendingCmds) {
642 for (PendingCmd pendingCmd : mPendingCmds) {
643 if (pendingCmd.cmdNum == cmdNum) {
644 found = pendingCmd;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800645 break;
646 }
647 }
648 if (found == null) {
649 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700650 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800651 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700652 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800653 " >= " + mMaxCount);
654 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700655 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800656 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700657 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700658 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800659 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700660 found = new PendingCmd(cmdNum, null);
661 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 remove 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 try {
669 found.responses.put(response);
670 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800671 }
672
673 // note that the timeout does not count time in deep sleep. If you don't want
674 // the device to sleep, hold a wakelock
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700675 public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700676 PendingCmd found = null;
677 synchronized (mPendingCmds) {
678 for (PendingCmd pendingCmd : mPendingCmds) {
679 if (pendingCmd.cmdNum == cmdNum) {
680 found = pendingCmd;
681 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800682 }
683 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700684 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700685 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700686 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800687 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700688 found.availableResponseCount--;
689 // if a matching add call has already retrieved this we can remove this
690 // instance from our list
691 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800692 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700693 NativeDaemonEvent result = null;
694 try {
695 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
696 } catch (InterruptedException e) {}
697 if (result == null) {
698 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
699 }
700 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800701 }
702
703 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
704 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700705 synchronized (mPendingCmds) {
706 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700707 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800708 }
709 }
710 }
711 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800712}