blob: 28013bd7b35bbab0d108bd5ab514b70a3bf09296 [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
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.OutputStream;
San Mehat67bd2cd2010-01-12 12:18:49 -080030import java.util.ArrayList;
San Mehat67bd2cd2010-01-12 12:18:49 -080031import java.util.concurrent.BlockingQueue;
32import java.util.concurrent.LinkedBlockingQueue;
33
34/**
35 * Generic connector class for interfacing with a native
36 * daemon which uses the libsysutils FrameworkListener
37 * protocol.
38 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070039final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
San Mehat1ff43712010-02-04 15:09:02 -080040 private static final boolean LOCAL_LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080041
42 private BlockingQueue<String> mResponseQueue;
43 private OutputStream mOutputStream;
44 private String TAG = "NativeDaemonConnector";
45 private String mSocket;
46 private INativeDaemonConnectorCallbacks mCallbacks;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070047 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080048
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070049 /** Lock held whenever communicating with native daemon. */
50 private Object mDaemonLock = new Object();
51
Kenny Root961aa8c2010-03-22 18:02:45 -070052 private final int BUFFER_SIZE = 4096;
53
San Mehat67bd2cd2010-01-12 12:18:49 -080054 class ResponseCode {
55 public static final int ActionInitiated = 100;
56
57 public static final int CommandOkay = 200;
58
59 // The range of 400 -> 599 is reserved for cmd failures
60 public static final int OperationFailed = 400;
61 public static final int CommandSyntaxError = 500;
62 public static final int CommandParameterError = 501;
63
64 public static final int UnsolicitedInformational = 600;
65
66 //
67 public static final int FailedRangeStart = 400;
68 public static final int FailedRangeEnd = 599;
69 }
70
71 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
72 String socket, int responseQueueSize, String logTag) {
73 mCallbacks = callbacks;
74 if (logTag != null)
75 TAG = logTag;
76 mSocket = socket;
77 mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
78 }
79
Chia-chi Yehe5750a32011-08-03 14:42:11 -070080 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080081 public void run() {
Chia-chi Yehe5750a32011-08-03 14:42:11 -070082 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
83 thread.start();
84 mCallbackHandler = new Handler(thread.getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080085
86 while (true) {
87 try {
88 listenToSocket();
89 } catch (Exception e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080090 Slog.e(TAG, "Error in NativeDaemonConnector", e);
San Mehat4c27e0e2010-01-29 05:22:17 -080091 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080092 }
93 }
94 }
95
Chia-chi Yehe5750a32011-08-03 14:42:11 -070096 @Override
97 public boolean handleMessage(Message msg) {
98 String event = (String) msg.obj;
99 try {
100 if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
101 Slog.w(TAG, String.format(
102 "Unhandled event '%s'", event));
103 }
104 } catch (Exception e) {
105 Slog.e(TAG, String.format(
106 "Error handling '%s'", event), e);
107 }
108 return true;
109 }
110
San Mehat4c27e0e2010-01-29 05:22:17 -0800111 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700112 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800113
114 try {
115 socket = new LocalSocket();
116 LocalSocketAddress address = new LocalSocketAddress(mSocket,
117 LocalSocketAddress.Namespace.RESERVED);
118
119 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800120
121 InputStream inputStream = socket.getInputStream();
122 mOutputStream = socket.getOutputStream();
123
anga030bc882011-02-01 14:10:25 +0100124 mCallbacks.onDaemonConnected();
125
Kenny Root961aa8c2010-03-22 18:02:45 -0700126 byte[] buffer = new byte[BUFFER_SIZE];
127 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800128
129 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700130 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
San Mehat67bd2cd2010-01-12 12:18:49 -0800131 if (count < 0) break;
132
Kenny Root12da9d72010-09-02 22:18:14 -0700133 // Add our starting point to the count and reset the start.
134 count += start;
135 start = 0;
136
San Mehat67bd2cd2010-01-12 12:18:49 -0800137 for (int i = 0; i < count; i++) {
138 if (buffer[i] == 0) {
139 String event = new String(buffer, start, i - start);
Joe Onorato8a9b2202010-02-26 18:56:32 -0800140 if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
San Mehat67bd2cd2010-01-12 12:18:49 -0800141
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700142 String[] tokens = event.split(" ", 2);
San Mehat67bd2cd2010-01-12 12:18:49 -0800143 try {
144 int code = Integer.parseInt(tokens[0]);
145
146 if (code >= ResponseCode.UnsolicitedInformational) {
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700147 mCallbackHandler.sendMessage(
148 mCallbackHandler.obtainMessage(code, event));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800149 } else {
150 try {
151 mResponseQueue.put(event);
152 } catch (InterruptedException ex) {
153 Slog.e(TAG, "Failed to put response onto queue", ex);
154 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800155 }
156 } catch (NumberFormatException nfe) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800157 Slog.w(TAG, String.format("Bad msg (%s)", event));
San Mehat67bd2cd2010-01-12 12:18:49 -0800158 }
159 start = i + 1;
160 }
161 }
Kenny Root12da9d72010-09-02 22:18:14 -0700162
163 // We should end at the amount we read. If not, compact then
164 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700165 if (start != count) {
166 final int remaining = BUFFER_SIZE - start;
167 System.arraycopy(buffer, start, buffer, 0, remaining);
168 start = remaining;
169 } else {
170 start = 0;
171 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800172 }
173 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800174 Slog.e(TAG, "Communications error", ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800175 throw ex;
176 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700177 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800178 if (mOutputStream != null) {
179 try {
180 mOutputStream.close();
181 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800182 Slog.w(TAG, "Failed closing output stream", e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800183 }
184 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800185 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800186 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800187
San Mehat4c27e0e2010-01-29 05:22:17 -0800188 try {
189 if (socket != null) {
190 socket.close();
191 }
192 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800193 Slog.w(TAG, "Failed closing socket", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800194 }
195 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800196 }
197
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700198 private void sendCommandLocked(String command) throws NativeDaemonConnectorException {
199 sendCommandLocked(command, null);
San Mehat67bd2cd2010-01-12 12:18:49 -0800200 }
201
202 /**
203 * Sends a command to the daemon with a single argument
204 *
205 * @param command The command to send to the daemon
206 * @param argument The argument to send with the command (or null)
207 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700208 private void sendCommandLocked(String command, String argument)
209 throws NativeDaemonConnectorException {
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700210 if (command != null && command.indexOf('\0') >= 0) {
211 throw new IllegalArgumentException("unexpected command: " + command);
212 }
213 if (argument != null && argument.indexOf('\0') >= 0) {
214 throw new IllegalArgumentException("unexpected argument: " + argument);
215 }
216
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700217 if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
218 if (mOutputStream == null) {
219 Slog.e(TAG, "No connection to daemon", new IllegalStateException());
220 throw new NativeDaemonConnectorException("No output stream!");
221 } else {
222 StringBuilder builder = new StringBuilder(command);
223 if (argument != null) {
224 builder.append(argument);
225 }
226 builder.append('\0');
San Mehat67bd2cd2010-01-12 12:18:49 -0800227
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700228 try {
229 mOutputStream.write(builder.toString().getBytes());
230 } catch (IOException ex) {
231 Slog.e(TAG, "IOException in sendCommand", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800232 }
233 }
234 }
235
San Mehatdeba6932010-01-20 15:14:31 -0800236 /**
237 * Issue a command to the native daemon and return the responses
238 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700239 public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
240 synchronized (mDaemonLock) {
241 return doCommandLocked(cmd);
242 }
243 }
244
245 private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException {
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800246 mResponseQueue.clear();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700247 sendCommandLocked(cmd);
San Mehat67bd2cd2010-01-12 12:18:49 -0800248
249 ArrayList<String> response = new ArrayList<String>();
250 boolean complete = false;
251 int code = -1;
252
253 while (!complete) {
254 try {
Robert Greenwalte5c3afb2010-09-22 14:32:35 -0700255 // TODO - this should not block forever
San Mehat67bd2cd2010-01-12 12:18:49 -0800256 String line = mResponseQueue.take();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800257 if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
San Mehat67bd2cd2010-01-12 12:18:49 -0800258 String[] tokens = line.split(" ");
259 try {
260 code = Integer.parseInt(tokens[0]);
261 } catch (NumberFormatException nfe) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800262 throw new NativeDaemonConnectorException(
San Mehat67bd2cd2010-01-12 12:18:49 -0800263 String.format("Invalid response from daemon (%s)", line));
264 }
265
San Mehatec4caa02010-02-03 10:48:21 -0800266 if ((code >= 200) && (code < 600)) {
San Mehat67bd2cd2010-01-12 12:18:49 -0800267 complete = true;
San Mehatec4caa02010-02-03 10:48:21 -0800268 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800269 response.add(line);
270 } catch (InterruptedException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800271 Slog.e(TAG, "Failed to process response", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800272 }
273 }
274
275 if (code >= ResponseCode.FailedRangeStart &&
276 code <= ResponseCode.FailedRangeEnd) {
San Mehatec4caa02010-02-03 10:48:21 -0800277 /*
278 * Note: The format of the last response in this case is
279 * "NNN <errmsg>"
280 */
281 throw new NativeDaemonConnectorException(
282 code, cmd, response.get(response.size()-1).substring(4));
San Mehat67bd2cd2010-01-12 12:18:49 -0800283 }
284 return response;
285 }
San Mehatdeba6932010-01-20 15:14:31 -0800286
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700287 /**
San Mehatdeba6932010-01-20 15:14:31 -0800288 * Issues a list command and returns the cooked list
289 */
290 public String[] doListCommand(String cmd, int expectedResponseCode)
San Mehat4c27e0e2010-01-29 05:22:17 -0800291 throws NativeDaemonConnectorException {
San Mehatdeba6932010-01-20 15:14:31 -0800292
293 ArrayList<String> rsp = doCommand(cmd);
294 String[] rdata = new String[rsp.size()-1];
295 int idx = 0;
296
San Mehat1ff43712010-02-04 15:09:02 -0800297 for (int i = 0; i < rsp.size(); i++) {
298 String line = rsp.get(i);
San Mehatdeba6932010-01-20 15:14:31 -0800299 try {
300 String[] tok = line.split(" ");
301 int code = Integer.parseInt(tok[0]);
302 if (code == expectedResponseCode) {
San Mehat80120b42010-01-26 12:48:39 -0800303 rdata[idx++] = line.substring(tok[0].length() + 1);
San Mehatdeba6932010-01-20 15:14:31 -0800304 } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800305 if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
San Mehat4086f752010-02-17 09:03:29 -0800306 int last = rsp.size() -1;
307 if (i != last) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800308 Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
San Mehat4086f752010-02-17 09:03:29 -0800309 for (int j = i; j <= last ; j++) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800310 Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
San Mehat4086f752010-02-17 09:03:29 -0800311 }
San Mehat1ff43712010-02-04 15:09:02 -0800312 }
San Mehatdeba6932010-01-20 15:14:31 -0800313 return rdata;
314 } else {
San Mehat4c27e0e2010-01-29 05:22:17 -0800315 throw new NativeDaemonConnectorException(
San Mehatdeba6932010-01-20 15:14:31 -0800316 String.format("Expected list response %d, but got %d",
317 expectedResponseCode, code));
318 }
319 } catch (NumberFormatException nfe) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800320 throw new NativeDaemonConnectorException(
321 String.format("Error reading code '%s'", line));
San Mehatdeba6932010-01-20 15:14:31 -0800322 }
323 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800324 throw new NativeDaemonConnectorException("Got an empty response");
San Mehatdeba6932010-01-20 15:14:31 -0800325 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700326
327 /** {@inheritDoc} */
328 public void monitor() {
329 synchronized (mDaemonLock) { }
330 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800331}