blob: 1e98f938fc8ca4d0afd1f2362403cef5e10b5ace [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;
San Mehat67bd2cd2010-01-12 12:18:49 -080032import java.util.ArrayList;
San Mehat67bd2cd2010-01-12 12:18:49 -080033import java.util.concurrent.BlockingQueue;
34import java.util.concurrent.LinkedBlockingQueue;
35
36/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080037 * Generic connector class for interfacing with a native daemon which uses the
38 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080039 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070040final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Jeff Sharkey31c6e482011-11-18 17:09:01 -080041 private static final boolean LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080042
Jeff Sharkey31c6e482011-11-18 17:09:01 -080043 private final String TAG;
44
45 private String mSocket;
46 private OutputStream mOutputStream;
47
48 private final BlockingQueue<NativeDaemonEvent> mResponseQueue;
49
San Mehat67bd2cd2010-01-12 12:18:49 -080050 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080051 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080052
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070053 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080054 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070055
Kenny Root961aa8c2010-03-22 18:02:45 -070056 private final int BUFFER_SIZE = 4096;
57
Jeff Sharkey31c6e482011-11-18 17:09:01 -080058 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
59 int responseQueueSize, String logTag) {
San Mehat67bd2cd2010-01-12 12:18:49 -080060 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080061 mSocket = socket;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080062 mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize);
63 TAG = logTag != null ? logTag : "NativeDaemonConnector";
San Mehat67bd2cd2010-01-12 12:18:49 -080064 }
65
Chia-chi Yehe5750a32011-08-03 14:42:11 -070066 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080067 public void run() {
Chia-chi Yehe5750a32011-08-03 14:42:11 -070068 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
69 thread.start();
70 mCallbackHandler = new Handler(thread.getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080071
72 while (true) {
73 try {
74 listenToSocket();
75 } catch (Exception e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080076 Slog.e(TAG, "Error in NativeDaemonConnector", e);
San Mehat4c27e0e2010-01-29 05:22:17 -080077 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080078 }
79 }
80 }
81
Chia-chi Yehe5750a32011-08-03 14:42:11 -070082 @Override
83 public boolean handleMessage(Message msg) {
84 String event = (String) msg.obj;
85 try {
86 if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
87 Slog.w(TAG, String.format(
88 "Unhandled event '%s'", event));
89 }
90 } catch (Exception e) {
91 Slog.e(TAG, String.format(
92 "Error handling '%s'", event), e);
93 }
94 return true;
95 }
96
San Mehat4c27e0e2010-01-29 05:22:17 -080097 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -070098 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -080099
100 try {
101 socket = new LocalSocket();
102 LocalSocketAddress address = new LocalSocketAddress(mSocket,
103 LocalSocketAddress.Namespace.RESERVED);
104
105 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800106
107 InputStream inputStream = socket.getInputStream();
108 mOutputStream = socket.getOutputStream();
109
anga030bc882011-02-01 14:10:25 +0100110 mCallbacks.onDaemonConnected();
111
Kenny Root961aa8c2010-03-22 18:02:45 -0700112 byte[] buffer = new byte[BUFFER_SIZE];
113 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800114
115 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700116 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
San Mehat67bd2cd2010-01-12 12:18:49 -0800117 if (count < 0) break;
118
Kenny Root12da9d72010-09-02 22:18:14 -0700119 // Add our starting point to the count and reset the start.
120 count += start;
121 start = 0;
122
San Mehat67bd2cd2010-01-12 12:18:49 -0800123 for (int i = 0; i < count; i++) {
124 if (buffer[i] == 0) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800125 final String rawEvent = new String(buffer, start, i - start);
126 if (LOGD) Slog.d(TAG, "RCV <- " + rawEvent);
San Mehat67bd2cd2010-01-12 12:18:49 -0800127
San Mehat67bd2cd2010-01-12 12:18:49 -0800128 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800129 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
130 rawEvent);
131 if (event.isClassUnsolicited()) {
132 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
133 event.getCode(), event.getRawEvent()));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800134 } else {
135 try {
136 mResponseQueue.put(event);
137 } catch (InterruptedException ex) {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800138 Slog.e(TAG, "Failed to put response onto queue: " + ex);
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800139 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800140 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800141 } catch (IllegalArgumentException e) {
142 Slog.w(TAG, "Problem parsing message: " + rawEvent, e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800143 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800144
San Mehat67bd2cd2010-01-12 12:18:49 -0800145 start = i + 1;
146 }
147 }
Kenny Root12da9d72010-09-02 22:18:14 -0700148
149 // We should end at the amount we read. If not, compact then
150 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700151 if (start != count) {
152 final int remaining = BUFFER_SIZE - start;
153 System.arraycopy(buffer, start, buffer, 0, remaining);
154 start = remaining;
155 } else {
156 start = 0;
157 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800158 }
159 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800160 Slog.e(TAG, "Communications error", ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800161 throw ex;
162 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700163 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800164 if (mOutputStream != null) {
165 try {
166 mOutputStream.close();
167 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800168 Slog.w(TAG, "Failed closing output stream", e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800169 }
170 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800171 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800172 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800173
San Mehat4c27e0e2010-01-29 05:22:17 -0800174 try {
175 if (socket != null) {
176 socket.close();
177 }
178 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800179 Slog.w(TAG, "Failed closing socket", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800180 }
181 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800182 }
183
San Mehat67bd2cd2010-01-12 12:18:49 -0800184 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800185 * Send command to daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800186 *
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800187 * @return the final command issued.
San Mehat67bd2cd2010-01-12 12:18:49 -0800188 */
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800189 private String sendCommandLocked(String cmd, Object... args)
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700190 throws NativeDaemonConnectorException {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800191 // TODO: eventually enforce that cmd doesn't contain arguments
192 if (cmd.indexOf('\0') >= 0) {
193 throw new IllegalArgumentException("unexpected command: " + cmd);
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700194 }
195
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800196 final StringBuilder builder = new StringBuilder(cmd);
197 for (Object arg : args) {
198 final String argString = String.valueOf(arg);
199 if (argString.indexOf('\0') >= 0) {
200 throw new IllegalArgumentException("unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700201 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800202
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800203 builder.append(' ');
204 appendEscaped(builder, argString);
205 }
206
207 final String unterminated = builder.toString();
208 if (LOGD) Slog.d(TAG, "SND -> " + unterminated);
209
210 builder.append('\0');
211
212 if (mOutputStream == null) {
213 throw new NativeDaemonConnectorException("missing output stream");
214 } else {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700215 try {
216 mOutputStream.write(builder.toString().getBytes());
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800217 } catch (IOException e) {
218 throw new NativeDaemonConnectorException("problem sending command", e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800219 }
220 }
221
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800222 return unterminated;
San Mehat67bd2cd2010-01-12 12:18:49 -0800223 }
San Mehatdeba6932010-01-20 15:14:31 -0800224
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700225 /**
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800226 * Issue a command to the native daemon and return the responses.
San Mehatdeba6932010-01-20 15:14:31 -0800227 */
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800228 public NativeDaemonEvent[] execute(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800229 throws NativeDaemonConnectorException {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800230 synchronized (mDaemonLock) {
231 return executeLocked(cmd, args);
232 }
233 }
San Mehatdeba6932010-01-20 15:14:31 -0800234
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800235 private NativeDaemonEvent[] executeLocked(String cmd, Object... args)
236 throws NativeDaemonConnectorException {
237 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
San Mehatdeba6932010-01-20 15:14:31 -0800238
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800239 mResponseQueue.clear();
240
241 final String sentCommand = sendCommandLocked(cmd, args);
242
243 NativeDaemonEvent event = null;
244 do {
San Mehatdeba6932010-01-20 15:14:31 -0800245 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800246 event = mResponseQueue.take();
247 } catch (InterruptedException e) {
248 Slog.w(TAG, "interrupted waiting for event line");
249 continue;
250 }
251 events.add(event);
252 } while (event.isClassContinue());
253
254 if (event.isClassClientError()) {
255 throw new NativeDaemonArgumentException(sentCommand, event);
256 }
257 if (event.isClassServerError()) {
258 throw new NativeDaemonFailureException(sentCommand, event);
259 }
260
261 return events.toArray(new NativeDaemonEvent[events.size()]);
262 }
263
264 /**
265 * Issue a command to the native daemon and return the raw responses.
266 *
267 * @deprecated callers should move to {@link #execute(String, Object...)}
268 * which returns parsed {@link NativeDaemonEvent}.
269 */
270 @Deprecated
271 public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
272 final ArrayList<String> rawEvents = Lists.newArrayList();
273 final NativeDaemonEvent[] events = execute(cmd);
274 for (NativeDaemonEvent event : events) {
275 rawEvents.add(event.getRawEvent());
276 }
277 return rawEvents;
278 }
279
280 /**
281 * Issues a list command and returns the cooked list of all
282 * {@link NativeDaemonEvent#getMessage()} which match requested code.
283 */
284 public String[] doListCommand(String cmd, int expectedCode)
285 throws NativeDaemonConnectorException {
286 final ArrayList<String> list = Lists.newArrayList();
287
288 final NativeDaemonEvent[] events = execute(cmd);
289 for (int i = 0; i < events.length - 1; i++) {
290 final NativeDaemonEvent event = events[i];
291 final int code = event.getCode();
292 if (code == expectedCode) {
293 list.add(event.getMessage());
294 } else {
San Mehat4c27e0e2010-01-29 05:22:17 -0800295 throw new NativeDaemonConnectorException(
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800296 "unexpected list response " + code + " instead of " + expectedCode);
San Mehatdeba6932010-01-20 15:14:31 -0800297 }
298 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800299
300 final NativeDaemonEvent finalEvent = events[events.length - 1];
301 if (!finalEvent.isClassOk()) {
302 throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
303 }
304
305 return list.toArray(new String[list.size()]);
306 }
307
308 /**
309 * Append the given argument to {@link StringBuilder}, escaping as needed,
310 * and surrounding with quotes when it contains spaces.
311 */
312 // @VisibleForTesting
313 static void appendEscaped(StringBuilder builder, String arg) {
314 final boolean hasSpaces = arg.indexOf(' ') >= 0;
315 if (hasSpaces) {
316 builder.append('"');
317 }
318
319 final int length = arg.length();
320 for (int i = 0; i < length; i++) {
321 final char c = arg.charAt(i);
322
323 if (c == '"') {
324 builder.append("\\\"");
325 } else if (c == '\\') {
326 builder.append("\\\\");
327 } else {
328 builder.append(c);
329 }
330 }
331
332 if (hasSpaces) {
333 builder.append('"');
334 }
335 }
336
337 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
338 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
339 super(command, event);
340 }
341
342 @Override
343 public IllegalArgumentException rethrowAsParcelableException() {
344 throw new IllegalArgumentException(getMessage(), this);
345 }
346 }
347
348 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
349 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
350 super(command, event);
351 }
San Mehatdeba6932010-01-20 15:14:31 -0800352 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700353
354 /** {@inheritDoc} */
355 public void monitor() {
356 synchronized (mDaemonLock) { }
357 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800358}