blob: cc2bcd9235608556746ebbb808fb757d040b7c0c [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;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070021import android.os.Handler;
22import android.os.HandlerThread;
23import android.os.Message;
San Mehat67bd2cd2010-01-12 12:18:49 -080024import android.os.SystemClock;
Joe Onorato8a9b2202010-02-26 18:56:32 -080025import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080026
Jeff Sharkey31c6e482011-11-18 17:09:01 -080027import com.google.android.collect.Lists;
28
San Mehat67bd2cd2010-01-12 12:18:49 -080029import java.io.IOException;
30import java.io.InputStream;
31import java.io.OutputStream;
Jeff Sharkeyba2896e2011-11-30 18:13:54 -080032import java.nio.charset.Charsets;
San Mehat67bd2cd2010-01-12 12:18:49 -080033import java.util.ArrayList;
San Mehat67bd2cd2010-01-12 12:18:49 -080034import java.util.concurrent.BlockingQueue;
35import java.util.concurrent.LinkedBlockingQueue;
36
37/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080038 * Generic connector class for interfacing with a native daemon which uses the
39 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080040 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070041final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Jeff Sharkey31c6e482011-11-18 17:09:01 -080042 private static final boolean LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080043
Jeff Sharkey31c6e482011-11-18 17:09:01 -080044 private final String TAG;
45
46 private String mSocket;
47 private OutputStream mOutputStream;
48
49 private final BlockingQueue<NativeDaemonEvent> mResponseQueue;
50
San Mehat67bd2cd2010-01-12 12:18:49 -080051 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080052 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080053
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070054 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080055 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070056
Kenny Root961aa8c2010-03-22 18:02:45 -070057 private final int BUFFER_SIZE = 4096;
58
Jeff Sharkey31c6e482011-11-18 17:09:01 -080059 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
60 int responseQueueSize, String logTag) {
San Mehat67bd2cd2010-01-12 12:18:49 -080061 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080062 mSocket = socket;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080063 mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize);
64 TAG = logTag != null ? logTag : "NativeDaemonConnector";
San Mehat67bd2cd2010-01-12 12:18:49 -080065 }
66
Chia-chi Yehe5750a32011-08-03 14:42:11 -070067 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080068 public void run() {
Chia-chi Yehe5750a32011-08-03 14:42:11 -070069 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
70 thread.start();
71 mCallbackHandler = new Handler(thread.getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080072
73 while (true) {
74 try {
75 listenToSocket();
76 } catch (Exception e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080077 Slog.e(TAG, "Error in NativeDaemonConnector", e);
San Mehat4c27e0e2010-01-29 05:22:17 -080078 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080079 }
80 }
81 }
82
Chia-chi Yehe5750a32011-08-03 14:42:11 -070083 @Override
84 public boolean handleMessage(Message msg) {
85 String event = (String) msg.obj;
86 try {
87 if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
88 Slog.w(TAG, String.format(
89 "Unhandled event '%s'", event));
90 }
91 } catch (Exception e) {
92 Slog.e(TAG, String.format(
93 "Error handling '%s'", event), e);
94 }
95 return true;
96 }
97
San Mehat4c27e0e2010-01-29 05:22:17 -080098 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -070099 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800100
101 try {
102 socket = new LocalSocket();
103 LocalSocketAddress address = new LocalSocketAddress(mSocket,
104 LocalSocketAddress.Namespace.RESERVED);
105
106 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800107
108 InputStream inputStream = socket.getInputStream();
109 mOutputStream = socket.getOutputStream();
110
anga030bc882011-02-01 14:10:25 +0100111 mCallbacks.onDaemonConnected();
112
Kenny Root961aa8c2010-03-22 18:02:45 -0700113 byte[] buffer = new byte[BUFFER_SIZE];
114 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800115
116 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700117 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
San Mehat67bd2cd2010-01-12 12:18:49 -0800118 if (count < 0) break;
119
Kenny Root12da9d72010-09-02 22:18:14 -0700120 // Add our starting point to the count and reset the start.
121 count += start;
122 start = 0;
123
San Mehat67bd2cd2010-01-12 12:18:49 -0800124 for (int i = 0; i < count; i++) {
125 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800126 final String rawEvent = new String(
127 buffer, start, i - start, Charsets.UTF_8);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800128 if (LOGD) Slog.d(TAG, "RCV <- " + rawEvent);
San Mehat67bd2cd2010-01-12 12:18:49 -0800129
San Mehat67bd2cd2010-01-12 12:18:49 -0800130 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800131 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
132 rawEvent);
133 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800134 // TODO: migrate to sending NativeDaemonEvent instances
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800135 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
136 event.getCode(), event.getRawEvent()));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800137 } else {
138 try {
139 mResponseQueue.put(event);
140 } catch (InterruptedException ex) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800141 Slog.e(TAG, "Failed to put response onto queue: " + ex);
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800142 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800143 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800144 } catch (IllegalArgumentException e) {
145 Slog.w(TAG, "Problem parsing message: " + rawEvent, e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800146 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800147
San Mehat67bd2cd2010-01-12 12:18:49 -0800148 start = i + 1;
149 }
150 }
Kenny Root12da9d72010-09-02 22:18:14 -0700151
152 // We should end at the amount we read. If not, compact then
153 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700154 if (start != count) {
155 final int remaining = BUFFER_SIZE - start;
156 System.arraycopy(buffer, start, buffer, 0, remaining);
157 start = remaining;
158 } else {
159 start = 0;
160 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800161 }
162 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800163 Slog.e(TAG, "Communications error", ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800164 throw ex;
165 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700166 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800167 if (mOutputStream != null) {
168 try {
169 mOutputStream.close();
170 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800171 Slog.w(TAG, "Failed closing output stream", e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800172 }
173 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800174 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800175 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800176
San Mehat4c27e0e2010-01-29 05:22:17 -0800177 try {
178 if (socket != null) {
179 socket.close();
180 }
181 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800182 Slog.w(TAG, "Failed closing socket", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800183 }
184 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800185 }
186
San Mehat67bd2cd2010-01-12 12:18:49 -0800187 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800188 * Send command to daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800189 *
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800190 * @return the final command issued.
San Mehat67bd2cd2010-01-12 12:18:49 -0800191 */
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800192 private String sendCommandLocked(String cmd, Object... args)
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700193 throws NativeDaemonConnectorException {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800194 // TODO: eventually enforce that cmd doesn't contain arguments
195 if (cmd.indexOf('\0') >= 0) {
196 throw new IllegalArgumentException("unexpected command: " + cmd);
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700197 }
198
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800199 final StringBuilder builder = new StringBuilder(cmd);
200 for (Object arg : args) {
201 final String argString = String.valueOf(arg);
202 if (argString.indexOf('\0') >= 0) {
203 throw new IllegalArgumentException("unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700204 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800205
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800206 builder.append(' ');
207 appendEscaped(builder, argString);
208 }
209
210 final String unterminated = builder.toString();
211 if (LOGD) Slog.d(TAG, "SND -> " + unterminated);
212
213 builder.append('\0');
214
215 if (mOutputStream == null) {
216 throw new NativeDaemonConnectorException("missing output stream");
217 } else {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700218 try {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800219 mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8));
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800220 } catch (IOException e) {
221 throw new NativeDaemonConnectorException("problem sending command", e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800222 }
223 }
224
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800225 return unterminated;
San Mehat67bd2cd2010-01-12 12:18:49 -0800226 }
San Mehatdeba6932010-01-20 15:14:31 -0800227
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700228 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800229 * Issue the given command to the native daemon and return a single expected
230 * response.
231 *
232 * @throws NativeDaemonConnectorException when problem communicating with
233 * native daemon, or if the response matches
234 * {@link NativeDaemonEvent#isClassClientError()} or
235 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800236 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800237 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
238 return execute(cmd.mCmd, cmd.mArguments.toArray());
239 }
240
241 /**
242 * Issue the given command to the native daemon and return a single expected
243 * response.
244 *
245 * @throws NativeDaemonConnectorException when problem communicating with
246 * native daemon, or if the response matches
247 * {@link NativeDaemonEvent#isClassClientError()} or
248 * {@link NativeDaemonEvent#isClassServerError()}.
249 */
250 public NativeDaemonEvent execute(String cmd, Object... args)
251 throws NativeDaemonConnectorException {
252 final NativeDaemonEvent[] events = executeForList(cmd, args);
253 if (events.length != 1) {
254 throw new NativeDaemonConnectorException(
255 "Expected exactly one response, but received " + events.length);
256 }
257 return events[0];
258 }
259
260 /**
261 * Issue the given command to the native daemon and return any
262 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
263 * final terminal response.
264 *
265 * @throws NativeDaemonConnectorException when problem communicating with
266 * native daemon, or if the response matches
267 * {@link NativeDaemonEvent#isClassClientError()} or
268 * {@link NativeDaemonEvent#isClassServerError()}.
269 */
270 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
271 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
272 }
273
274 /**
275 * Issue the given command to the native daemon and return any
276 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
277 * final terminal response.
278 *
279 * @throws NativeDaemonConnectorException when problem communicating with
280 * native daemon, or if the response matches
281 * {@link NativeDaemonEvent#isClassClientError()} or
282 * {@link NativeDaemonEvent#isClassServerError()}.
283 */
284 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800285 throws NativeDaemonConnectorException {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800286 synchronized (mDaemonLock) {
287 return executeLocked(cmd, args);
288 }
289 }
San Mehatdeba6932010-01-20 15:14:31 -0800290
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800291 private NativeDaemonEvent[] executeLocked(String cmd, Object... args)
292 throws NativeDaemonConnectorException {
293 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
San Mehatdeba6932010-01-20 15:14:31 -0800294
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800295 mResponseQueue.clear();
296
297 final String sentCommand = sendCommandLocked(cmd, args);
298
299 NativeDaemonEvent event = null;
300 do {
San Mehatdeba6932010-01-20 15:14:31 -0800301 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800302 event = mResponseQueue.take();
303 } catch (InterruptedException e) {
304 Slog.w(TAG, "interrupted waiting for event line");
305 continue;
306 }
307 events.add(event);
308 } while (event.isClassContinue());
309
310 if (event.isClassClientError()) {
311 throw new NativeDaemonArgumentException(sentCommand, event);
312 }
313 if (event.isClassServerError()) {
314 throw new NativeDaemonFailureException(sentCommand, event);
315 }
316
317 return events.toArray(new NativeDaemonEvent[events.size()]);
318 }
319
320 /**
321 * Issue a command to the native daemon and return the raw responses.
322 *
323 * @deprecated callers should move to {@link #execute(String, Object...)}
324 * which returns parsed {@link NativeDaemonEvent}.
325 */
326 @Deprecated
327 public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
328 final ArrayList<String> rawEvents = Lists.newArrayList();
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800329 final NativeDaemonEvent[] events = executeForList(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800330 for (NativeDaemonEvent event : events) {
331 rawEvents.add(event.getRawEvent());
332 }
333 return rawEvents;
334 }
335
336 /**
337 * Issues a list command and returns the cooked list of all
338 * {@link NativeDaemonEvent#getMessage()} which match requested code.
339 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800340 @Deprecated
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800341 public String[] doListCommand(String cmd, int expectedCode)
342 throws NativeDaemonConnectorException {
343 final ArrayList<String> list = Lists.newArrayList();
344
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800345 final NativeDaemonEvent[] events = executeForList(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800346 for (int i = 0; i < events.length - 1; i++) {
347 final NativeDaemonEvent event = events[i];
348 final int code = event.getCode();
349 if (code == expectedCode) {
350 list.add(event.getMessage());
351 } else {
San Mehat4c27e0e2010-01-29 05:22:17 -0800352 throw new NativeDaemonConnectorException(
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800353 "unexpected list response " + code + " instead of " + expectedCode);
San Mehatdeba6932010-01-20 15:14:31 -0800354 }
355 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800356
357 final NativeDaemonEvent finalEvent = events[events.length - 1];
358 if (!finalEvent.isClassOk()) {
359 throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
360 }
361
362 return list.toArray(new String[list.size()]);
363 }
364
365 /**
366 * Append the given argument to {@link StringBuilder}, escaping as needed,
367 * and surrounding with quotes when it contains spaces.
368 */
369 // @VisibleForTesting
370 static void appendEscaped(StringBuilder builder, String arg) {
371 final boolean hasSpaces = arg.indexOf(' ') >= 0;
372 if (hasSpaces) {
373 builder.append('"');
374 }
375
376 final int length = arg.length();
377 for (int i = 0; i < length; i++) {
378 final char c = arg.charAt(i);
379
380 if (c == '"') {
381 builder.append("\\\"");
382 } else if (c == '\\') {
383 builder.append("\\\\");
384 } else {
385 builder.append(c);
386 }
387 }
388
389 if (hasSpaces) {
390 builder.append('"');
391 }
392 }
393
394 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
395 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
396 super(command, event);
397 }
398
399 @Override
400 public IllegalArgumentException rethrowAsParcelableException() {
401 throw new IllegalArgumentException(getMessage(), this);
402 }
403 }
404
405 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
406 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
407 super(command, event);
408 }
San Mehatdeba6932010-01-20 15:14:31 -0800409 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700410
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800411 /**
412 * Command builder that handles argument list building.
413 */
414 public static class Command {
415 private String mCmd;
416 private ArrayList<Object> mArguments = Lists.newArrayList();
417
418 public Command(String cmd, Object... args) {
419 mCmd = cmd;
420 for (Object arg : args) {
421 appendArg(arg);
422 }
423 }
424
425 public Command appendArg(Object arg) {
426 mArguments.add(arg);
427 return this;
428 }
429 }
430
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700431 /** {@inheritDoc} */
432 public void monitor() {
433 synchronized (mDaemonLock) { }
434 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800435}