blob: eac767f7355c628ba421c2922aa787fe8202c969 [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;
Tetsutoki Shiozawae83724e2017-07-13 15:37:40 +090027import android.os.SystemProperties;
Robert Greenwalt470fd722012-01-18 12:51:15 -080028import android.util.LocalLog;
Joe Onorato8a9b2202010-02-26 18:56:32 -080029import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080030
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080031import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkey8948c012015-11-03 12:33:54 -080032import com.android.internal.util.Preconditions;
Tetsutoki Shiozawae83724e2017-07-13 15:37:40 +090033import com.android.server.power.ShutdownThread;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080034import com.google.android.collect.Lists;
35
Robert Greenwalt470fd722012-01-18 12:51:15 -080036import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080037import java.io.IOException;
38import java.io.InputStream;
39import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080040import java.io.PrintWriter;
Elliott Hughesd396a442013-06-28 16:24:48 -070041import java.nio.charset.StandardCharsets;
San Mehat67bd2cd2010-01-12 12:18:49 -080042import java.util.ArrayList;
Robert Greenwalt470007f2012-02-07 11:36:55 -080043import java.util.concurrent.atomic.AtomicInteger;
Robert Greenwaltef215992012-06-05 11:48:40 -070044import java.util.concurrent.ArrayBlockingQueue;
45import java.util.concurrent.BlockingQueue;
Rebecca Silbersteinefdb8452016-04-21 12:14:41 -070046import java.util.concurrent.CountDownLatch;
Robert Greenwaltef215992012-06-05 11:48:40 -070047import java.util.concurrent.TimeUnit;
Robert Greenwalt470007f2012-02-07 11:36:55 -080048import java.util.LinkedList;
Daulet Zhanguzinea1a7ca2020-01-03 09:46:50 +000049import java.util.Objects;
San Mehat67bd2cd2010-01-12 12:18:49 -080050
51/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080052 * Generic connector class for interfacing with a native daemon which uses the
53 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080054 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070055final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Robert Greenwalt7f44ff82014-05-07 23:49:08 -070056 private final static boolean VDBG = false;
57
Jeff Sharkey31c6e482011-11-18 17:09:01 -080058 private final String TAG;
59
60 private String mSocket;
61 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080062 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080063
Jeff Sharkey48877892015-03-18 11:27:19 -070064 private volatile boolean mDebug = false;
Jeff Sharkey8948c012015-11-03 12:33:54 -080065 private volatile Object mWarnIfHeld;
Jeff Sharkey48877892015-03-18 11:27:19 -070066
Robert Greenwalt470007f2012-02-07 11:36:55 -080067 private final ResponseQueue mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080068
Dianne Hackborn77b987f2014-02-26 16:20:52 -080069 private final PowerManager.WakeLock mWakeLock;
70
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070071 private final Looper mLooper;
72
San Mehat67bd2cd2010-01-12 12:18:49 -080073 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080074 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080075
Robert Greenwalt470007f2012-02-07 11:36:55 -080076 private AtomicInteger mSequenceNumber;
77
Jeff Sharkey14cbe522015-07-08 14:06:37 -070078 private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
Robert Greenwalt5a0c3202012-05-22 16:07:46 -070079 private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
Robert Greenwalt470007f2012-02-07 11:36:55 -080080
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070081 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080082 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070083
Kenny Root961aa8c2010-03-22 18:02:45 -070084 private final int BUFFER_SIZE = 4096;
85
Jeff Sharkey31c6e482011-11-18 17:09:01 -080086 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Dianne Hackborn77b987f2014-02-26 16:20:52 -080087 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -070088 this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
89 FgThread.get().getLooper());
90 }
91
92 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
93 int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
94 Looper looper) {
San Mehat67bd2cd2010-01-12 12:18:49 -080095 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080096 mSocket = socket;
Robert Greenwalt470007f2012-02-07 11:36:55 -080097 mResponseQueue = new ResponseQueue(responseQueueSize);
Dianne Hackborn77b987f2014-02-26 16:20:52 -080098 mWakeLock = wl;
99 if (mWakeLock != null) {
100 mWakeLock.setReferenceCounted(true);
101 }
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -0700102 mLooper = looper;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800103 mSequenceNumber = new AtomicInteger(0);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800104 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -0800105 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -0800106 }
107
Jeff Sharkey48877892015-03-18 11:27:19 -0700108 /**
109 * Enable Set debugging mode, which causes messages to also be written to both
110 * {@link Slog} in addition to internal log.
111 */
112 public void setDebug(boolean debug) {
113 mDebug = debug;
114 }
115
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800116 /**
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900117 * Like SystemClock.uptimeMillis, except truncated to an int so it will fit in a message arg.
118 * Inaccurate across 49.7 days of uptime, but only used for debugging.
119 */
120 private int uptimeMillisInt() {
121 return (int) SystemClock.uptimeMillis() & Integer.MAX_VALUE;
122 }
123
124 /**
Jeff Sharkey8948c012015-11-03 12:33:54 -0800125 * Yell loudly if someone tries making future {@link #execute(Command)}
126 * calls while holding a lock on the given object.
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800127 */
Jeff Sharkey8948c012015-11-03 12:33:54 -0800128 public void setWarnIfHeld(Object warnIfHeld) {
129 Preconditions.checkState(mWarnIfHeld == null);
Daulet Zhanguzinea1a7ca2020-01-03 09:46:50 +0000130 mWarnIfHeld = Objects.requireNonNull(warnIfHeld);
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800131 }
132
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700133 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -0800134 public void run() {
Dianne Hackborn2ffa11e2014-04-21 15:56:18 -0700135 mCallbackHandler = new Handler(mLooper, this);
San Mehat67bd2cd2010-01-12 12:18:49 -0800136
137 while (true) {
Tetsutoki Shiozawabe12b812017-10-27 14:03:00 +0900138 if (isShuttingDown()) break;
San Mehat67bd2cd2010-01-12 12:18:49 -0800139 try {
140 listenToSocket();
141 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800142 loge("Error in NativeDaemonConnector: " + e);
Tetsutoki Shiozawabe12b812017-10-27 14:03:00 +0900143 if (isShuttingDown()) break;
San Mehat4c27e0e2010-01-29 05:22:17 -0800144 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -0800145 }
146 }
147 }
148
Tetsutoki Shiozawabe12b812017-10-27 14:03:00 +0900149 private static boolean isShuttingDown() {
150 String shutdownAct = SystemProperties.get(
151 ShutdownThread.SHUTDOWN_ACTION_PROPERTY, "");
152 return shutdownAct != null && shutdownAct.length() > 0;
153 }
154
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700155 @Override
156 public boolean handleMessage(Message msg) {
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900157 final String event = (String) msg.obj;
158 final int start = uptimeMillisInt();
159 final int sent = msg.arg1;
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700160 try {
Robert Greenwalt2d34b4a2012-04-20 13:08:02 -0700161 if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800162 log(String.format("Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700163 }
164 } catch (Exception e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800165 loge("Error handling '" + event + "': " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800166 } finally {
Dianne Hackborn4590e522014-03-24 13:36:46 -0700167 if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800168 mWakeLock.release();
169 }
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900170 final int end = uptimeMillisInt();
171 if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
172 loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
173 }
174 if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
175 loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
176 }
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700177 }
178 return true;
179 }
180
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900181 private LocalSocketAddress determineSocketAddress() {
182 // If we're testing, set up a socket in a namespace that's accessible to test code.
183 // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
184 // production devices, even if said native daemons ill-advisedly pick a socket name that
185 // starts with __test__, only allow this on debug builds.
186 if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
187 return new LocalSocketAddress(mSocket);
188 } else {
189 return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
190 }
191 }
192
San Mehat4c27e0e2010-01-29 05:22:17 -0800193 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700194 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800195
196 try {
197 socket = new LocalSocket();
Lorenzo Colitti7421a012013-08-20 22:51:24 +0900198 LocalSocketAddress address = determineSocketAddress();
San Mehat67bd2cd2010-01-12 12:18:49 -0800199
200 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800201
202 InputStream inputStream = socket.getInputStream();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800203 synchronized (mDaemonLock) {
204 mOutputStream = socket.getOutputStream();
205 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800206
anga030bc882011-02-01 14:10:25 +0100207 mCallbacks.onDaemonConnected();
208
Daichi Hironodda65522015-11-19 16:58:57 +0900209 FileDescriptor[] fdList = null;
Kenny Root961aa8c2010-03-22 18:02:45 -0700210 byte[] buffer = new byte[BUFFER_SIZE];
211 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800212
213 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700214 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800215 if (count < 0) {
216 loge("got " + count + " reading with start = " + start);
217 break;
218 }
Daichi Hironodda65522015-11-19 16:58:57 +0900219 fdList = socket.getAncillaryFileDescriptors();
San Mehat67bd2cd2010-01-12 12:18:49 -0800220
Kenny Root12da9d72010-09-02 22:18:14 -0700221 // Add our starting point to the count and reset the start.
222 count += start;
223 start = 0;
224
San Mehat67bd2cd2010-01-12 12:18:49 -0800225 for (int i = 0; i < count; i++) {
226 if (buffer[i] == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800227 // Note - do not log this raw message since it may contain
228 // sensitive data
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800229 final String rawEvent = new String(
Elliott Hughesd396a442013-06-28 16:24:48 -0700230 buffer, start, i - start, StandardCharsets.UTF_8);
San Mehat67bd2cd2010-01-12 12:18:49 -0800231
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800232 boolean releaseWl = false;
San Mehat67bd2cd2010-01-12 12:18:49 -0800233 try {
Daichi Hironodda65522015-11-19 16:58:57 +0900234 final NativeDaemonEvent event =
235 NativeDaemonEvent.parseRawEvent(rawEvent, fdList);
Paul Lawrencec38182f2014-11-11 12:23:22 -0800236
237 log("RCV <- {" + event + "}");
238
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800239 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800240 // TODO: migrate to sending NativeDaemonEvent instances
Dianne Hackborn4590e522014-03-24 13:36:46 -0700241 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
242 && mWakeLock != null) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800243 mWakeLock.acquire();
244 releaseWl = true;
245 }
Lorenzo Colitticd63d242016-04-10 15:39:53 +0900246 Message msg = mCallbackHandler.obtainMessage(
247 event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
248 if (mCallbackHandler.sendMessage(msg)) {
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800249 releaseWl = false;
250 }
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800251 } else {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800252 mResponseQueue.add(event.getCmdNumber(), event);
San Mehat67bd2cd2010-01-12 12:18:49 -0800253 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800254 } catch (IllegalArgumentException e) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800255 log("Problem parsing message " + e);
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800256 } finally {
257 if (releaseWl) {
Lorenzo Colittiae107af2016-04-11 17:08:10 +0900258 mWakeLock.release();
Dianne Hackborn77b987f2014-02-26 16:20:52 -0800259 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800260 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800261
San Mehat67bd2cd2010-01-12 12:18:49 -0800262 start = i + 1;
263 }
264 }
Paul Lawrencec38182f2014-11-11 12:23:22 -0800265
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800266 if (start == 0) {
Paul Lawrencec38182f2014-11-11 12:23:22 -0800267 log("RCV incomplete");
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800268 }
Kenny Root12da9d72010-09-02 22:18:14 -0700269
270 // We should end at the amount we read. If not, compact then
271 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700272 if (start != count) {
273 final int remaining = BUFFER_SIZE - start;
274 System.arraycopy(buffer, start, buffer, 0, remaining);
275 start = remaining;
276 } else {
277 start = 0;
278 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800279 }
280 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800281 loge("Communications error: " + ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800282 throw ex;
283 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700284 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800285 if (mOutputStream != null) {
286 try {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800287 loge("closing stream for " + mSocket);
San Mehat4c27e0e2010-01-29 05:22:17 -0800288 mOutputStream.close();
289 } catch (IOException e) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800290 loge("Failed closing output stream: " + e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800291 }
292 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800293 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800294 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800295
San Mehat4c27e0e2010-01-29 05:22:17 -0800296 try {
297 if (socket != null) {
298 socket.close();
299 }
300 } catch (IOException ex) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800301 loge("Failed closing socket: " + ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800302 }
303 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800304 }
305
San Mehat67bd2cd2010-01-12 12:18:49 -0800306 /**
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700307 * Wrapper around argument that indicates it's sensitive and shouldn't be
308 * logged.
309 */
310 public static class SensitiveArg {
311 private final Object mArg;
312
313 public SensitiveArg(Object arg) {
314 mArg = arg;
315 }
316
317 @Override
318 public String toString() {
319 return String.valueOf(mArg);
320 }
321 }
322
323 /**
Robert Greenwalt470007f2012-02-07 11:36:55 -0800324 * Make command for daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800325 */
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700326 @VisibleForTesting
327 static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
328 String cmd, Object... args) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800329 if (cmd.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800330 throw new IllegalArgumentException("Unexpected command: " + cmd);
331 }
332 if (cmd.indexOf(' ') >= 0) {
333 throw new IllegalArgumentException("Arguments must be separate from command");
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700334 }
335
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700336 rawBuilder.append(sequenceNumber).append(' ').append(cmd);
337 logBuilder.append(sequenceNumber).append(' ').append(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800338 for (Object arg : args) {
339 final String argString = String.valueOf(arg);
340 if (argString.indexOf('\0') >= 0) {
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800341 throw new IllegalArgumentException("Unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700342 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800343
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700344 rawBuilder.append(' ');
345 logBuilder.append(' ');
346
347 appendEscaped(rawBuilder, argString);
348 if (arg instanceof SensitiveArg) {
349 logBuilder.append("[scrubbed]");
350 } else {
351 appendEscaped(logBuilder, argString);
352 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800353 }
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700354
355 rawBuilder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800356 }
San Mehatdeba6932010-01-20 15:14:31 -0800357
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700358 /**
Rebecca Silbersteinefdb8452016-04-21 12:14:41 -0700359 * Method that waits until all asychronous notifications sent by the native daemon have
360 * been processed. This method must not be called on the notification thread or an
361 * exception will be thrown.
362 */
363 public void waitForCallbacks() {
364 if (Thread.currentThread() == mLooper.getThread()) {
365 throw new IllegalStateException("Must not call this method on callback thread");
366 }
367
368 final CountDownLatch latch = new CountDownLatch(1);
369 mCallbackHandler.post(new Runnable() {
370 @Override
371 public void run() {
372 latch.countDown();
373 }
374 });
375 try {
376 latch.await();
377 } catch (InterruptedException e) {
378 Slog.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
379 }
380 }
381
382 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800383 * Issue the given command to the native daemon and return a single expected
384 * response.
385 *
386 * @throws NativeDaemonConnectorException when problem communicating with
387 * native daemon, or if the response matches
388 * {@link NativeDaemonEvent#isClassClientError()} or
389 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800390 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800391 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
392 return execute(cmd.mCmd, cmd.mArguments.toArray());
393 }
394
395 /**
396 * Issue the given command to the native daemon and return a single expected
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800397 * response. Any arguments must be separated from base command so they can
398 * be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800399 *
400 * @throws NativeDaemonConnectorException when problem communicating with
401 * native daemon, or if the response matches
402 * {@link NativeDaemonEvent#isClassClientError()} or
403 * {@link NativeDaemonEvent#isClassServerError()}.
404 */
405 public NativeDaemonEvent execute(String cmd, Object... args)
406 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700407 return execute(DEFAULT_TIMEOUT, cmd, args);
408 }
409
410 public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
411 throws NativeDaemonConnectorException {
412 final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800413 if (events.length != 1) {
414 throw new NativeDaemonConnectorException(
415 "Expected exactly one response, but received " + events.length);
416 }
417 return events[0];
418 }
419
420 /**
421 * Issue the given command to the native daemon and return any
422 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
423 * final terminal response.
424 *
425 * @throws NativeDaemonConnectorException when problem communicating with
426 * native daemon, or if the response matches
427 * {@link NativeDaemonEvent#isClassClientError()} or
428 * {@link NativeDaemonEvent#isClassServerError()}.
429 */
430 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
431 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
432 }
433
434 /**
435 * Issue the given command to the native daemon and return any
436 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800437 * final terminal response. Any arguments must be separated from base
438 * command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800439 *
440 * @throws NativeDaemonConnectorException when problem communicating with
441 * native daemon, or if the response matches
442 * {@link NativeDaemonEvent#isClassClientError()} or
443 * {@link NativeDaemonEvent#isClassServerError()}.
444 */
445 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800446 throws NativeDaemonConnectorException {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700447 return executeForList(DEFAULT_TIMEOUT, cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800448 }
San Mehatdeba6932010-01-20 15:14:31 -0800449
Robert Greenwalt470007f2012-02-07 11:36:55 -0800450 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800451 * Issue the given command to the native daemon and return any {@linke
452 * NativeDaemonEvent@isClassContinue()} responses, including the final
453 * terminal response. Note that the timeout does not count time in deep
454 * sleep. Any arguments must be separated from base command so they can be
455 * properly escaped.
Robert Greenwalt470007f2012-02-07 11:36:55 -0800456 *
457 * @throws NativeDaemonConnectorException when problem communicating with
458 * native daemon, or if the response matches
459 * {@link NativeDaemonEvent#isClassClientError()} or
460 * {@link NativeDaemonEvent#isClassServerError()}.
461 */
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700462 public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800463 throws NativeDaemonConnectorException {
Jeff Sharkey8948c012015-11-03 12:33:54 -0800464 if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
465 Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
466 + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
Jeff Sharkeye41dc592015-11-03 10:39:42 -0800467 }
468
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700469 final long startTime = SystemClock.elapsedRealtime();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700470
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700471 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Robert Greenwaltd1925982012-03-12 15:37:40 -0700472
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700473 final StringBuilder rawBuilder = new StringBuilder();
474 final StringBuilder logBuilder = new StringBuilder();
475 final int sequenceNumber = mSequenceNumber.incrementAndGet();
476
477 makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
478
479 final String rawCmd = rawBuilder.toString();
480 final String logCmd = logBuilder.toString();
481
Robert Greenwaltd1925982012-03-12 15:37:40 -0700482 log("SND -> {" + logCmd + "}");
483
Robert Greenwaltd1925982012-03-12 15:37:40 -0700484 synchronized (mDaemonLock) {
485 if (mOutputStream == null) {
486 throw new NativeDaemonConnectorException("missing output stream");
487 } else {
488 try {
Elliott Hughesb8292832013-06-28 16:50:13 -0700489 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
Robert Greenwaltd1925982012-03-12 15:37:40 -0700490 } catch (IOException e) {
491 throw new NativeDaemonConnectorException("problem sending command", e);
492 }
493 }
494 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800495
496 NativeDaemonEvent event = null;
497 do {
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700498 event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800499 if (event == null) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700500 loge("timed-out waiting for response to " + logCmd);
Todd Kennedy8101ee62015-06-23 13:35:28 -0700501 throw new NativeDaemonTimeoutException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800502 }
Robert Greenwalt7f44ff82014-05-07 23:49:08 -0700503 if (VDBG) log("RMV <- {" + event + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800504 events.add(event);
505 } while (event.isClassContinue());
506
Robert Greenwalt5a0c3202012-05-22 16:07:46 -0700507 final long endTime = SystemClock.elapsedRealtime();
508 if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
509 loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
510 }
511
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800512 if (event.isClassClientError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700513 throw new NativeDaemonArgumentException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800514 }
515 if (event.isClassServerError()) {
Robert Greenwaltd1925982012-03-12 15:37:40 -0700516 throw new NativeDaemonFailureException(logCmd, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800517 }
518
519 return events.toArray(new NativeDaemonEvent[events.size()]);
520 }
521
522 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800523 * Append the given argument to {@link StringBuilder}, escaping as needed,
524 * and surrounding with quotes when it contains spaces.
525 */
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800526 @VisibleForTesting
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800527 static void appendEscaped(StringBuilder builder, String arg) {
528 final boolean hasSpaces = arg.indexOf(' ') >= 0;
529 if (hasSpaces) {
530 builder.append('"');
531 }
532
533 final int length = arg.length();
534 for (int i = 0; i < length; i++) {
535 final char c = arg.charAt(i);
536
537 if (c == '"') {
538 builder.append("\\\"");
539 } else if (c == '\\') {
540 builder.append("\\\\");
541 } else {
542 builder.append(c);
543 }
544 }
545
546 if (hasSpaces) {
547 builder.append('"');
548 }
549 }
550
551 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
552 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
553 super(command, event);
554 }
555
556 @Override
557 public IllegalArgumentException rethrowAsParcelableException() {
558 throw new IllegalArgumentException(getMessage(), this);
559 }
560 }
561
562 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
563 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
564 super(command, event);
565 }
San Mehatdeba6932010-01-20 15:14:31 -0800566 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700567
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800568 /**
Jeff Sharkey7b4596f2013-02-25 10:55:29 -0800569 * Command builder that handles argument list building. Any arguments must
570 * be separated from base command so they can be properly escaped.
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800571 */
572 public static class Command {
573 private String mCmd;
574 private ArrayList<Object> mArguments = Lists.newArrayList();
575
576 public Command(String cmd, Object... args) {
577 mCmd = cmd;
578 for (Object arg : args) {
579 appendArg(arg);
580 }
581 }
582
583 public Command appendArg(Object arg) {
584 mArguments.add(arg);
585 return this;
586 }
587 }
588
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700589 /** {@inheritDoc} */
590 public void monitor() {
591 synchronized (mDaemonLock) { }
592 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800593
594 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
595 mLocalLog.dump(fd, pw, args);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800596 pw.println();
597 mResponseQueue.dump(fd, pw, args);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800598 }
599
600 private void log(String logstring) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700601 if (mDebug) Slog.d(TAG, logstring);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800602 mLocalLog.log(logstring);
603 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800604
605 private void loge(String logstring) {
606 Slog.e(TAG, logstring);
607 mLocalLog.log(logstring);
608 }
609
610 private static class ResponseQueue {
611
Robert Greenwaltef215992012-06-05 11:48:40 -0700612 private static class PendingCmd {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700613 public final int cmdNum;
614 public final String logCmd;
615
Robert Greenwaltef215992012-06-05 11:48:40 -0700616 public BlockingQueue<NativeDaemonEvent> responses =
617 new ArrayBlockingQueue<NativeDaemonEvent>(10);
Robert Greenwaltef215992012-06-05 11:48:40 -0700618
619 // The availableResponseCount member is used to track when we can remove this
620 // instance from the ResponseQueue.
621 // This is used under the protection of a sync of the mPendingCmds object.
622 // A positive value means we've had more writers retreive this object while
623 // a negative value means we've had more readers. When we've had an equal number
624 // (it goes to zero) we can remove this object from the mPendingCmds list.
625 // Note that we may have more responses for this command (and more readers
626 // coming), but that would result in a new PendingCmd instance being created
627 // and added with the same cmdNum.
628 // Also note that when this goes to zero it just means a parity of readers and
629 // writers have retrieved this object - not that they are done using it. The
630 // responses queue may well have more responses yet to be read or may get more
631 // responses added to it. But all those readers/writers have retreived and
632 // hold references to this instance already so it can be removed from
633 // mPendingCmds queue.
634 public int availableResponseCount;
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700635
636 public PendingCmd(int cmdNum, String logCmd) {
637 this.cmdNum = cmdNum;
638 this.logCmd = logCmd;
639 }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800640 }
641
Robert Greenwaltef215992012-06-05 11:48:40 -0700642 private final LinkedList<PendingCmd> mPendingCmds;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800643 private int mMaxCount;
644
645 ResponseQueue(int maxCount) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700646 mPendingCmds = new LinkedList<PendingCmd>();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800647 mMaxCount = maxCount;
648 }
649
650 public void add(int cmdNum, NativeDaemonEvent response) {
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;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800656 break;
657 }
658 }
659 if (found == null) {
660 // didn't find it - make sure our queue isn't too big before adding
Robert Greenwaltef215992012-06-05 11:48:40 -0700661 while (mPendingCmds.size() >= mMaxCount) {
Robert Greenwalt470007f2012-02-07 11:36:55 -0800662 Slog.e("NativeDaemonConnector.ResponseQueue",
Robert Greenwaltef215992012-06-05 11:48:40 -0700663 "more buffered than allowed: " + mPendingCmds.size() +
Robert Greenwalt470007f2012-02-07 11:36:55 -0800664 " >= " + mMaxCount);
665 // let any waiter timeout waiting for this
Robert Greenwaltef215992012-06-05 11:48:40 -0700666 PendingCmd pendingCmd = mPendingCmds.remove();
Robert Greenwalt470007f2012-02-07 11:36:55 -0800667 Slog.e("NativeDaemonConnector.ResponseQueue",
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700668 "Removing request: " + pendingCmd.logCmd + " (" +
Robert Greenwaltef215992012-06-05 11:48:40 -0700669 pendingCmd.cmdNum + ")");
Robert Greenwalt470007f2012-02-07 11:36:55 -0800670 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700671 found = new PendingCmd(cmdNum, null);
672 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800673 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700674 found.availableResponseCount++;
675 // if a matching remove call has already retrieved this we can remove this
676 // instance from our list
677 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800678 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700679 try {
680 found.responses.put(response);
681 } catch (InterruptedException e) { }
Robert Greenwalt470007f2012-02-07 11:36:55 -0800682 }
683
684 // note that the timeout does not count time in deep sleep. If you don't want
685 // the device to sleep, hold a wakelock
Jeff Sharkey14cbe522015-07-08 14:06:37 -0700686 public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
Robert Greenwaltef215992012-06-05 11:48:40 -0700687 PendingCmd found = null;
688 synchronized (mPendingCmds) {
689 for (PendingCmd pendingCmd : mPendingCmds) {
690 if (pendingCmd.cmdNum == cmdNum) {
691 found = pendingCmd;
692 break;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800693 }
694 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700695 if (found == null) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700696 found = new PendingCmd(cmdNum, logCmd);
Robert Greenwaltef215992012-06-05 11:48:40 -0700697 mPendingCmds.add(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800698 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700699 found.availableResponseCount--;
700 // if a matching add call has already retrieved this we can remove this
701 // instance from our list
702 if (found.availableResponseCount == 0) mPendingCmds.remove(found);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800703 }
Robert Greenwaltef215992012-06-05 11:48:40 -0700704 NativeDaemonEvent result = null;
705 try {
706 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
707 } catch (InterruptedException e) {}
708 if (result == null) {
709 Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
710 }
711 return result;
Robert Greenwalt470007f2012-02-07 11:36:55 -0800712 }
713
714 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
715 pw.println("Pending requests:");
Robert Greenwaltef215992012-06-05 11:48:40 -0700716 synchronized (mPendingCmds) {
717 for (PendingCmd pendingCmd : mPendingCmds) {
Jeff Sharkey56cd6462013-06-07 15:09:15 -0700718 pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
Robert Greenwalt470007f2012-02-07 11:36:55 -0800719 }
720 }
721 }
722 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800723}